|
1 """Mutual exclusion -- for use with module sched |
|
2 |
|
3 A mutex has two pieces of state -- a 'locked' bit and a queue. |
|
4 When the mutex is not locked, the queue is empty. |
|
5 Otherwise, the queue contains 0 or more (function, argument) pairs |
|
6 representing functions (or methods) waiting to acquire the lock. |
|
7 When the mutex is unlocked while the queue is not empty, |
|
8 the first queue entry is removed and its function(argument) pair called, |
|
9 implying it now has the lock. |
|
10 |
|
11 Of course, no multi-threading is implied -- hence the funny interface |
|
12 for lock, where a function is called once the lock is aquired. |
|
13 """ |
|
14 |
|
15 from collections import deque |
|
16 |
|
17 class mutex: |
|
18 def __init__(self): |
|
19 """Create a new mutex -- initially unlocked.""" |
|
20 self.locked = 0 |
|
21 self.queue = deque() |
|
22 |
|
23 def test(self): |
|
24 """Test the locked bit of the mutex.""" |
|
25 return self.locked |
|
26 |
|
27 def testandset(self): |
|
28 """Atomic test-and-set -- grab the lock if it is not set, |
|
29 return True if it succeeded.""" |
|
30 if not self.locked: |
|
31 self.locked = 1 |
|
32 return True |
|
33 else: |
|
34 return False |
|
35 |
|
36 def lock(self, function, argument): |
|
37 """Lock a mutex, call the function with supplied argument |
|
38 when it is acquired. If the mutex is already locked, place |
|
39 function and argument in the queue.""" |
|
40 if self.testandset(): |
|
41 function(argument) |
|
42 else: |
|
43 self.queue.append((function, argument)) |
|
44 |
|
45 def unlock(self): |
|
46 """Unlock a mutex. If the queue is not empty, call the next |
|
47 function with its argument.""" |
|
48 if self.queue: |
|
49 function, argument = self.queue.popleft() |
|
50 function(argument) |
|
51 else: |
|
52 self.locked = 0 |