简体   繁体   English

暂停两个 Python 线程,而第三个线程执行操作(带锁?)

[英]Pausing two Python threads while a third one does stuff (with locks?)

I'm new to concurrent programming.我是并发编程的新手。

I'd like to execute three tasks repeatedly.我想重复执行三个任务。 The first two should run all the time, the third should run every hour or so.前两个应该一直运行,第三个应该每小时运行一次。 The first two tasks can run in parallel, but I always want to pause them while the third task is running.前两个任务可以并行运行,但我总是想在第三个任务运行时暂停它们。

Here's the skeleton of what I've tried:这是我尝试过的框架:

import threading
import time

flock = threading.Lock()
glock = threading.Lock()

def f():
    while True:
        with flock:
            print 'f'
            time.sleep(1)

def g():
    while True:
        with glock:
            print 'g'
            time.sleep(1)

def h():
    while True:
        with flock:
            with glock:
                print 'h'
        time.sleep(5)

threading.Thread(target=f).start()
threading.Thread(target=g).start()
threading.Thread(target=h).start()

I would expect this code to print an f and ag every second, and an h about every five seconds.我希望这段代码每秒打印一个 f 和 ag,大约每五秒打印一个 h。 However, when I run it it takes around 12 f's and 12 g's before I start seeing some h's.然而,当我运行它时,在我开始看到一些 h 之前需要大约 12 f 和 12 g。 It's looks like the first two threads constantly release and re-acquire their locks while the third thread is left out of the loop.看起来前两个线程不断释放并重新获取它们的锁,而第三个线程则被排除在循环之外。

  1. Why is that?这是为什么? When the third thread tries to acquire a currently held lock, and it is then released, shouldn't acquisition immediately succeed instead of the first/second thread immediately acquiring it again?当第三个线程尝试获取当前持有的锁,然后将其释放时,是否应该立即获取成功而不是第一个/第二个线程立即再次获取它? I am probably misunderstanding something.我可能误解了一些东西。
  2. What would be a good way to achieve what I want?什么是实现我想要的好方法?

Note: moving the time.sleep(1) calls out of the with flock/glock block works for this simple example, but apparently not for my real application where the threads spend most of their time doing the actual operations.注意:将time.sleep(1)调用移出 with flock/glock 块适用于这个简单的示例,但显然不适用于线程花费大部分时间进行实际操作的实际应用程序。 When the first two threads sleep for a second after each execution of the loop body, with the lock released, the third task still never gets executed.当前两个线程在每次执行循环体后休眠一秒时,当锁被释放时,第三个任务仍然不会被执行。

How about do it with threading.Events :如何使用threading.Events 来实现

import threading
import time
import logging

logger=logging.getLogger(__name__)

def f(resume,is_waiting,name):
    while True:
        if not resume.is_set():
            is_waiting.set()
            logger.debug('{n} pausing...'.format(n=name))
            resume.wait()
            is_waiting.clear()
        logger.info(name)
        time.sleep(1)

def h(resume,waiters):
    while True:
        logger.debug('halt') 
        resume.clear()
        for i,w in enumerate(waiters):
            logger.debug('{i}: wait for worker to pause'.format(i=i))
            w.wait()
        logger.info('h begin')
        time.sleep(2)
        logger.info('h end')        
        logger.debug('resume')
        resume.set()
        time.sleep(5)

logging.basicConfig(level=logging.DEBUG,
                    format='[%(asctime)s %(threadName)s] %(message)s',
                    datefmt='%H:%M:%S')

# set means resume; clear means halt
resume = threading.Event()
resume.set()

waiters=[]
for name in 'fg':
    is_waiting=threading.Event()
    waiters.append(is_waiting)
    threading.Thread(target=f,args=(resume,is_waiting,name)).start()    
threading.Thread(target=h,args=(resume,waiters)).start()

yields产量

[07:28:55 Thread-1] f
[07:28:55 Thread-2] g
[07:28:55 Thread-3] halt
[07:28:55 Thread-3] 0: wait for worker to pause
[07:28:56 Thread-1] f pausing...
[07:28:56 Thread-2] g pausing...
[07:28:56 Thread-3] 1: wait for worker to pause
[07:28:56 Thread-3] h begin
[07:28:58 Thread-3] h end
[07:28:58 Thread-3] resume
[07:28:58 Thread-1] f
[07:28:58 Thread-2] g
[07:28:59 Thread-1] f
[07:28:59 Thread-2] g
[07:29:00 Thread-1] f
[07:29:00 Thread-2] g
[07:29:01 Thread-1] f
[07:29:01 Thread-2] g
[07:29:02 Thread-1] f
[07:29:02 Thread-2] g
[07:29:03 Thread-3] halt

(In response to a question in the comments) This code tries to measure how long it takes for the h -thread to acquire each lock from the other worker threads. (回应评论中的一个问题)这段代码试图测量h线程从其他工作线程获取每个锁所需的时间。

It seems to show that even if h is waiting to acquire a lock, the other worker thread may with fairly high probability release and reacquire the lock.这似乎表明,即使h正在等待获取锁,其他工作线程也可能以相当高的概率释放并重新获取锁。 There is no priority given to h just because it has been waiting longer.没有优先考虑h仅仅因为它等待的时间更长。

David Beazley has presented at PyCon about problems related to threading and the GIL. David Beazley 在 PyCon 上介绍了与线程和 GIL 相关的问题。 Here is a pdf of the slides .这是幻灯片pdf 文件 It is a fascinating read and may help explain this as well.这是一本引人入胜的读物,也可能有助于解释这一点。

import threading
import time
import logging

logger=logging.getLogger(__name__)

def f(lock,n):
    while True:
        with lock:
            logger.info(n)
            time.sleep(1)

def h(locks):
    while True:
        t=time.time()
        for n,lock in enumerate(locks):
            lock.acquire()
            t2=time.time()
            logger.info('h acquired {n}: {d}'.format(n=n,d=t2-t))
            t=t2
        t2=time.time()
        logger.info('h {d}'.format(d=t2-t))
        t=t2
        for lock in locks:
            lock.release()
        time.sleep(5)

logging.basicConfig(level=logging.DEBUG,
                    format='[%(asctime)s %(threadName)s] %(message)s',
                    datefmt='%H:%M:%S')

locks=[]
N=5
for n in range(N):
    lock=threading.Lock()
    locks.append(lock)
    t=threading.Thread(target=f,args=(lock,n))
    t.start()

threading.Thread(target=h,args=(locks,)).start()

The simplest way to do this is with 3 Python processes.最简单的方法是使用 3 个 Python 进程。 If you are doing this on Linux, then the hourly process can send a signal to cause the other tasks to pause, or you could even kill them and then restart after the hourly task is complete.如果您在 Linux 上执行此操作,则每小时进程可以发送信号以导致其他任务暂停,或者您甚至可以杀死它们,然后在每小时任务完成后重新启动。 No need for threads.不需要线程。

However, if you are determined to use threads, then try to share NO data whatsoever between threads, just send messages back and forth (also know as data copying rather than data sharing).但是,如果你有决心使用线程,然后尝试共享任何线程之间,只需发送短信来回(也称为数据复制,而不是数据共享)数据。 Threading is hard to get right.线程很难做到正确。

But, multiple processes forces you to share nothing, and is therefore much easier to do correctly.但是,多个进程迫使您不共享任何内容,因此更容易正确执行。 If you use a library like 0MQ http://www.zeromq.org to do your message passing, then it is easy to move from a threading model to a multiple process model.如果您使用像 0MQ http://www.zeromq.org这样的库来进行消息传递,那么很容易从线程模型转移到多进程模型。

Using communication for synchronization:使用通信进行同步:

#!/usr/bin/env python
import threading
import time
from Queue import Empty, Queue

def f(q, c):
    while True:
        try: q.get_nowait(); q.get() # get PAUSE signal      
        except Empty: pass  # no signal, do our thing
        else: q.get()       # block until RESUME signal
        print c,
        time.sleep(1)

def h(queues):
    while True:
        for q in queues:
            q.put_nowait(1); q.put(1) # block until PAUSE received
        print 'h'
        for q in queues:
            q.put(1) # put RESUME
        time.sleep(5)

queues = [Queue(1) for _ in range(2)]
threading.Thread(target=f, args=(queues[0], 'f')).start()
threading.Thread(target=f, args=(queues[1], 'g')).start()
threading.Thread(target=h, args=(queues,)).start()

It might be not-optimal from a performance point of you but I find it much easier to follow.从您的性能角度来看,它可能不是最佳的,但我发现它更容易遵循。

Output输出

f g
f g h
f g f g g f f g g f g f f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h
f g f g f g f g f g f g f g h

What about this approach (although contentious because I know 'global' variables are supposedly a big no-no when it comes to threading (newbie-so still learning)...这种方法怎么样(虽然有争议,因为我知道“全局”变量在线程方面被认为是一个很大的禁忌(新手 - 所以仍在学习)......

import threading, time


import threading, time

def f():
    global BL
    while True:
        BL = 'f' if BL == -1 else BL
        if BL == 'f':
            print('f')
            BL = -1
            ss(0.1)

def g():
    global BL
    while True:
        BL = 'g' if BL == -1 else BL
        if BL == 'g':
            print('g')
            BL = -1
            ss(0.1)

def h():
    global BL
    while True:
        BL = 'h' if BL == -1 and (tt() - start) % delay_3rd <= 0.1 and (tt()-start) > 1 else BL
        if (BL == 'h'):
           print('h')
           print(f' seconds: {round(tt() - start,None)}!!'*100)
           BL = -1
           ss(0.1)


BL, delay_3rd, [ss], [tt]  = -1, 5, [time.sleep], [time.time]
start = tt()

3rd one will run every second (you could make delay_3rd = 3600 for hourly intervals; whereas 1st two run always (per your request/intention)第三个将每秒运行(您可以将 delay_3rd = 3600 设置为每小时间隔;而第一个两个始终运行(根据您的请求/意图)

threading.Thread(target=f).start()
threading.Thread(target=g).start()
threading.Thread(target=h).start()

(output after about 4-5 secs of running...) (运行约 4-5 秒后输出...)

f F

h seconds: 5!! h秒:5!!

g G

f F

g G

f F

f F

g G

f F

h H

g G

f F

g G

h seconds: 6!! h秒:6!!

f F

g G

f F

g G

f F

g G

f F

g G

f F

g G

f F

g G

f F

h H

seconds: 7!!秒:7!!

g G

f F

g G

(notice h only appears every second; f & g appear intermittently throughout...) (注意 h 仅每秒出现一次; f & g 始终间歇性出现...)

How about a semaphore initialized to 2?信号量初始化为 2 怎么样? F and G wait and signal one unit, H waits and signals 2 units. F 和 G 等待并发出 1 个单位的信号,H 等待并发出 2 个单位的信号。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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