簡體   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