繁体   English   中英

如何同步两个交替线程

[英]How to synchronize two alternating threads

我需要启动两个线程,控制哪个线程先启动,然后让它们交替工作。

以下代码使用do_sleep = True可以正常工作,但使用do_sleep = False可能会失败。

如果不使用那些丑陋(和不可靠)的睡眠,我怎样才能达到同样的效果?

它与do_sleep = True一起使用的原因是:

  • 每个工作线程在尝试获取锁并开始下一个作业之前给另一个线程启动的时间
  • 在第一个和第二个工作人员的启动之间有一个暂停,允许第一个工作人员在第二个工作人员准备好之前获得锁

使用do_sleep = False它可能会失败,因为:

  • 在每个作业结束时,每个线程可以尝试在另一个线程之前获取下一个周期的锁,执行两个连续的作业而不是交替执行
  • 第二个线程可以在第一个线程之前获得锁

这是代码:

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')

编辑Mike 建议的使用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

这是错误的输出,使用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

这是正确的输出,通过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

解决这个问题的几种方法。 一个相对简单的方法是使用锁来控制对一个单独的共享变量的访问:调用这个另一个变量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>

B 线程做同样的事情,只是反转if ownerowner =语句。 显然,您可以对其进行参数化,以便两者实际上只运行相同的代码。

编辑

这是工作版本,在对象中包含建议的逻辑:

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')

您可以排除错误线程获取锁的可能性,排除依赖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')

它按您指定的方式工作。 或者,您可以使用threading.Conditionacquirereleasewaitnotify / notifyAll的组合),但这会更加微妙,尤其是在哪个线程先行方面。

我已经尝试过 Gil Hamilton 的答案,如果我删除所有的睡眠,它对我不起作用。 我认为这是因为我的“主”线程不断获得优先权。 我发现同步两个或多个线程的更好方法是使用条件对象。

这是我的工作备用锁定对象,里面有条件对象

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()

这是一个示例用例(不需要线程中的睡眠语句):

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

这是标准输出:

    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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM