[英]How to synchronize two alternating threads
I need to start two threads, controlling which one starts first, then having them alternating their jobs.我需要启动两个线程,控制哪个线程先启动,然后让它们交替工作。
The following code works as expected with do_sleep = True
, but it can fail with do_sleep = False
.以下代码使用
do_sleep = True
可以正常工作,但使用do_sleep = False
可能会失败。
How can I achieve the same result without using those ugly (and unreliable) sleeps?如果不使用那些丑陋(和不可靠)的睡眠,我怎样才能达到同样的效果?
The reason why it works with do_sleep = True
is that:它与
do_sleep = True
一起使用的原因是:
With do_sleep = False
it can fail because:使用
do_sleep = False
它可能会失败,因为:
Here is the code:这是代码:
import threading
import time
import random
do_sleep = True
def workerA(lock):
for i in range(5):
lock.acquire()
print('Working A - %s' % i)
time.sleep(random.uniform(0.2, 1))
lock.release()
if do_sleep: time.sleep(0.1)
def workerB(lock):
for i in range(5):
if do_sleep: time.sleep(0.1)
lock.acquire()
print('Working B - %s' % i)
time.sleep(random.uniform(0.2, 1))
lock.release()
if do_sleep: time.sleep(0.1)
lock = threading.Lock()
t1 = threading.Thread(target=workerA, args=(lock, ))
t2 = threading.Thread(target=workerB, args=(lock, ))
t1.start()
if do_sleep: time.sleep(0.1)
t2.start()
t1.join()
t2.join()
print('done')
EDIT Using a Queue
as suggested by Mike doesn't help, because the first worker would finish the job without waiting for the second.编辑Mike 建议的使用
Queue
没有帮助,因为第一个工作人员会在不等待第二个工作人员的情况下完成工作。
This is the wrong output of a version after replacing the Lock
with a Queue
:这是用
Queue
替换Lock
后版本的错误输出:
Working A - 0
Working A - 1
Working B - 0
Working A - 2
Working B - 1
Working A - 3
Working B - 2
Working A - 4
Working B - 3
Working B - 4
done
This is the wrong output, obtained with do_sleep = False
:这是错误的输出,使用
do_sleep = False
获得:
Working A - 0
Working A - 1
Working A - 2
Working A - 3
Working A - 4
Working B - 0
Working B - 1
Working B - 2
Working B - 3
Working B - 4
done
This is the correct output, obtained with do_sleep = True
:这是正确的输出,通过
do_sleep = True
获得:
Working A - 0
Working B - 0
Working A - 1
Working B - 1
Working A - 2
Working B - 2
Working A - 3
Working B - 3
Working A - 4
Working B - 4
done
Several ways to solve this.解决这个问题的几种方法。 One relatively easy one is to use the lock to control access to a separate shared variable: call this other variable
owner
, it can either be set to A or B. Thread A can only start a job when owner
is set to A, and thread B can only start a job when owner
is set to B. Then the pseudo-code is (assume thread A here):一个相对简单的方法是使用锁来控制对一个单独的共享变量的访问:调用这个另一个变量
owner
,它可以设置为 A 或 B。只有当owner
设置为 A 时,线程 A 才能启动作业,并且线程B 只能在owner
设置为 B 的情况下启动作业。那么伪代码是(这里假设线程 A):
while True:
while True:
# Loop until I'm the owner
lock.acquire()
if owner == A:
break
lock.release()
# Now I'm the owner. And I still hold the lock. Start job.
<Grab next job (or start job or finish job, whatever is required to remove it from contention)>
owner = B
lock.release()
<Finish job if not already done. Go get next one>
The B thread does the same thing only reversing the if owner
and owner =
statements. B 线程做同样的事情,只是反转
if owner
和owner =
语句。 And obviously you can parameterize it so that both actually just run the same code.显然,您可以对其进行参数化,以便两者实际上只运行相同的代码。
EDIT编辑
Here is the working version, with the suggested logic inside an object:这是工作版本,在对象中包含建议的逻辑:
import threading
import time
def workerA(lock):
for i in range(5):
lock.acquire_for('A')
print('Start A - %s' % i)
time.sleep(0.5)
print('End A - %s' % i)
lock.release_to('B')
def workerB(lock):
for i in range(5):
lock.acquire_for('B')
print('Start B - %s' % i)
time.sleep(2)
print('End B - %s' % i)
lock.release_to('A')
class LockWithOwner:
lock = threading.RLock()
owner = 'A'
def acquire_for(self, owner):
n = 0
while True:
self.lock.acquire()
if self.owner == owner:
break
n += 1
self.lock.release()
time.sleep(0.001)
print('Waited for {} to be the owner {} times'.format(owner, n))
def release_to(self, new_owner):
self.owner = new_owner
self.lock.release()
lock = LockWithOwner()
lock.owner = 'A'
t1 = threading.Thread(target=workerA, args=(lock, ))
t2 = threading.Thread(target=workerB, args=(lock, ))
t1.start()
t2.start()
t1.join()
t2.join()
print('done')
You can exclude the possibility of the wrong thread acquiring the lock, exclude relying on time.sleep(...)
for correctness and shorten your code at the same time using Queue (two queues for both way communication):您可以排除错误线程获取锁的可能性,排除依赖
time.sleep(...)
的正确性并同时使用队列缩短代码(双向通信的两个队列):
import threading
import time
import random
from Queue import Queue
def work_hard(name, i):
print('start %s - %s' % (name, i))
time.sleep(random.uniform(0.2, 1))
print('end %s - %s' % (name, i))
def worker(name, q_mine, q_his):
for i in range(5):
q_mine.get()
work_hard(name, i)
q_his.put(1)
qAB = Queue()
qBA = Queue()
t1 = threading.Thread(target=worker, args=('A', qAB, qBA))
t2 = threading.Thread(target=worker, args=('B', qBA, qAB))
t1.start()
qAB.put(1) # notice how you don't need time.sleep(...) even here
t2.start()
t1.join()
t2.join()
print('done')
It works as you specified.它按您指定的方式工作。 Alternatively you can use
threading.Condition
(a combination of acquire
, release
, wait
and notify
/ notifyAll
), but that will be more subtle, especially in terms of which thread goes first.或者,您可以使用
threading.Condition
( acquire
、 release
、 wait
和notify
/ notifyAll
的组合),但这会更加微妙,尤其是在哪个线程先行方面。
I have tried Gil Hamilton's answer and it doesn't work for me if I remove all the sleeps.我已经尝试过 Gil Hamilton 的答案,如果我删除所有的睡眠,它对我不起作用。 I think it's because my 'main' thread keeps getting the priority.
我认为这是因为我的“主”线程不断获得优先权。 I found out that a better way to synchronize two or more threads is to use conditional object.
我发现同步两个或多个线程的更好方法是使用条件对象。
Here is my working alternate lock object with conditional object inside这是我的工作备用锁定对象,里面有条件对象
class AltLock():
def __init__(self, initial_thread):
self.allow = initial_thread
self.cond = threading.Condition()
def acquire_for(self, thread):
self.cond.acquire()
while self.allow!=thread:
print("\tLOCK:", thread, "waiting")
self.cond.wait()
print("\tLOCK:", thread, "acquired")
def release_to(self, thread):
print("\tLOCK: releasing to", thread)
self.allow=thread
self.cond.notifyAll()
self.cond.release()
And this is an example usecase (the sleep statements in the thread are not required):这是一个示例用例(不需要线程中的睡眠语句):
class MyClass():
def __init__(self):
self.lock = AltLock("main")
def _start(self):
print("thread: Started, wait 2 second")
time.sleep(2)
print("---")
self.lock.acquire_for("thread")
time.sleep(2)
print("---")
print("thread: start lock acquired")
self.lock.release_to("main")
return 0
def start(self):
self.lock.acquire_for("main")
self.thread = threading.Thread(target = self._start, )
self.thread.start()
print("main: releasing lock")
self.lock.release_to("thread")
self.lock.acquire_for("main")
print("main: lock acquired")
myclass = MyClass()
myclass.start()
myclass.lock.release_to("main") # house keeping
And this is stdout:这是标准输出:
LOCK: main acquired
thread: Started, wait 2 second
main: releasing lock
LOCK: releasing to thread
LOCK: main waiting // 'main' thread try to reacquire the lock immediately but get blocked by wait.
---
LOCK: thread acquired
---
thread: start lock acquired
LOCK: releasing to main
LOCK: main acquired
main: lock acquired
LOCK: releasing to main
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.