Python 使用'thread.join()时,多线程冻结`

Python 使用'thread.join()时,多线程冻结`,python,multithreading,multiprocessing,python-multithreading,Python,Multithreading,Multiprocessing,Python Multithreading,我正在尝试设置3个线程,并在队列中执行5个任务。其思想是线程首先同时运行前3个任务,然后2个线程完成剩余的2个任务。但该计划似乎停滞不前。我看不出它有什么毛病 from multiprocessing import Manager import threading import time global exitFlag exitFlag = 0 class myThread(threading.Thread): def __init__(self, threadID, name,

我正在尝试设置3个线程,并在队列中执行5个任务。其思想是线程首先同时运行前3个任务,然后2个线程完成剩余的2个任务。但该计划似乎停滞不前。我看不出它有什么毛病

from multiprocessing import Manager
import threading
import time
global exitFlag 
exitFlag = 0


class myThread(threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q

    def run(self):
        print("Starting " + self.name)
        process_data(self.name, self.q)
        print("Exiting " + self.name)


def process_data(threadName, q):
    global exitFlag
    while not exitFlag:
        if not workQueue.empty():
            data = q.get()
            print("%s processing %s" % (threadName, data))
        else:
            pass
        time.sleep(1)
    print('Nothing to Process')


threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Manager().Queue(10)
threads = []
threadID = 1

# create thread
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# fill up queue
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# wait queue clear
while not workQueue.empty():
    pass
# notify thread exit
exitFlag = 1
# wait for all threads to finish
for t in threads:
    t.join()
print("Exiting Main Thread")

我不知道到底发生了什么,但是在我删除了
join()
部分之后,程序就可以运行了。我不明白的是,exitFlag应该在队列清空时发出信号。因此,process_data()似乎没有检测到该信号。

您的代码存在多个问题。首先,由于全局解释器锁(),CPython中的线程不会“同时”运行Python代码。线程必须持有GIL才能执行Python字节码。默认情况下,如果一个线程因为阻塞I/O而没有更早地删除GIL,那么它最多可以保存5毫秒(Python 3.2+)。对于Python代码的并行执行,您必须使用
多处理

您还不必要地使用
管理器.Queue
而不是
队列.Queue
Manager.Queue
是单独的管理器进程上的
Queue.Queue
。您引入了一个迂回的IPC和内存复制,在这里没有任何好处

导致死锁的原因是您在此处存在竞争条件:

    if not workQueue.empty():
        data = q.get()
这不是一个原子操作。一个线程可以检查
workQueue.empty()
,然后放下GIL,让另一个线程排空队列,然后继续执行
data=q.get()
,如果不在队列上再次放置某些内容,它将永远阻塞
Queue.empty()
检查是一种通用的反模式,不需要使用它。使用毒药(哨兵值)来打破get循环,让工人知道他们应该退出。你需要和你的员工一样多的哨兵价值观。查找有关iter(callabel,sentinel)的更多信息

示例输出:

2019-02-14 17:58:18.265208螺纹-1开始
2019-02-14 17:58:18.265277线程一处理一
2019-02-14 17:58:18.265472螺纹-2开始
2019-02-14 17:58:18.265542线程二处理二
2019-02-14 17:58:18.265691螺纹-3开始
2019-02-14 17:58:18.265793线程三处理
2019-02-14 17:58:19.266417线程一处理四
2019-02-14 17:58:19.266632线程二处理五
2019-02-14 17:58:19.266767螺纹-3退出
2019-02-14 17:58:20.267588螺纹-1退出
2019-02-14 17:58:20.267861螺纹-2退出
2019-02-14 17:58:20.267994主线程退出
进程已完成,退出代码为0

如果您不坚持子类化
Thread
,您也可以使用
多处理.pool.ThreadPool
a.k.a.
多处理.dummy.pool
,它在后台为您提供管道。

您可能会感谢您的详细解释!非常感谢。我在读关于多线程的书时遇到了吉尔。但是,我对这个机制没有足够的了解,没有意识到这是个问题,只是一个次要问题。如果我想学习使用多线程/多处理进行开发,有没有好的参考资料?到目前为止,我一直在关注博客,并在这里和那里进行调整。@fnosdy GIL并不是真正的问题,这是比赛条件的问题。当您使用
Queue.empty()
作为控制流时,进程和
multiprocessing.Queue
可能会遇到同样的问题。@fnosdy作为参考…多线程和多处理是广泛而复杂的主题,您很难找到一个能为您提供一切的参考。例如,你将不得不通过搜索,搜索类似的howto。此外,还需要了解一些硬件和操作系统(wikipedia,stackoverflow)。尝试测试条件以确保操作会成功,然后假设操作肯定会成功是一种反模式。如果在测试和操作之间发生任何变化,则会增加复杂性和中断。相反,尝试该操作并在发生故障时处理故障。
import time
from queue import Queue
from datetime import datetime
from threading import Thread, current_thread


SENTINEL = 'SENTINEL'


class myThread(Thread):

    def __init__(self, func, inqueue):
        super().__init__()
        self.func = func
        self._inqueue = inqueue

    def run(self):
        print(f"{datetime.now()} {current_thread().name} starting")
        self.func(self._inqueue)
        print(f"{datetime.now()} {current_thread().name} exiting")


def process_data(_inqueue):
    for data in iter(_inqueue.get, SENTINEL):
        print(f"{datetime.now()} {current_thread().name} "
              f"processing {data}")
        time.sleep(1)


if __name__ == '__main__':


    N_WORKERS = 3

    inqueue = Queue()
    input_data = ["One", "Two", "Three", "Four", "Five"]

    sentinels = [SENTINEL] * N_WORKERS # one sentinel value per worker
    # enqueue input and sentinels
    for word in input_data +  sentinels:
        inqueue.put(word)

    threads = [myThread(process_data, inqueue) for _ in range(N_WORKERS)]

    for t in threads:
        t.start()
    for t in threads:
        t.join()

    print(f"{datetime.now()} {current_thread().name} exiting")