繁体   English   中英

Python threading.join()挂起

[英]Python threading.join() hangs

我的问题如下:我有一个从threading.Thread继承的类,我希望能够正常停止。 此类也有一个Queue,它是从中获取工作的。

由于项目中有很多类应该具有这种行为,因此我创建了一些超类来减少重复的代码,如下所示:

与线程相关的行为:

class StoppableThread(Thread):
def __init__(self):
    Thread.__init__(self)
    self._stop = Event()

def stop(self):
    self._stop.set()

def stopped(self):
    return self._stop.isSet()

队列相关行为:

class Queueable():
    def __init__(self):
        self._queue = Queue()

    def append_to_job_queue(self, job):
        self._queue.put(job)

结合以上两者,并将queue.join()添加到stop()调用中

class StoppableQueueThread(StoppableThread, Queueable):
    def __init__(self):
        StoppableThread.__init__(self)
        Queueable.__init__(self)

    def stop(self):
        super(StoppableQueueThread, self).stop()
        self._queue.join()

数据源的基类:

class DataSource(StoppableThread, ABC):

    def __init__(self, data_parser):
        StoppableThread.__init__(self)
        self.setName("DataSource")
        ABC.__init__(self)
        self._data_parser = data_parser

    def run(self):
        while not self.stopped():
            record = self._fetch_data()
            self._data_parser.append_to_job_queue(record)

    @abstractmethod
    def _fetch_data(self):
        """implement logic here for obtaining a data piece
            should return the fetched data"""

数据源的实现:

class CSVDataSource(DataSource):
    def __init__(self, data_parser, file_path):
        DataSource.__init__(self, data_parser)
        self.file_path = file_path
        self.csv_data = Queue()
        print('loading csv')
        self.load_csv()
        print('done loading csv')

    def load_csv(self):
        """Loops through csv and adds data to a queue"""
        with open(self.file_path, 'r') as f:

            self.reader = reader(f)
            next(self.reader, None)  # skip header
            for row in self.reader:
                self.csv_data.put(row)

    def _fetch_data(self):
        """Returns next item of the queue"""
        item = self.csv_data.get()
        self.csv_data.task_done()
        print(self.csv_data.qsize())
        return item

假设有一个名为dsCSVDataSource实例,如果我想停止我调用的线程:

ds.stop()
ds.join()

但是, ds.join()调用永远不会返回。 我不确定为什么会这样,因为run()方法会检查是否设置了stop事件。

有任何想法吗?

更新

根据要求,需要更多的清晰度:应用程序是由多个线程构成的。 RealStrategy线程(以下)是所有其他线程的所有者,并负责启动和终止它们。 我尚未为任何线程设置守护程序标志,因此默认情况下它们应为非守护程序。

主线程如下所示:

if __name__ == '__main__':
    def exit_handler(signal, frame):
        rs.stop_engine()
        rs.join()
        sys.exit(0)

    signal.signal(signal.SIGINT, exit_handler)



    rs = RealStrategy()
    rs.run_engine()

这里是rs.run_engine()rs.stop_engine()被调用方法主要有

class RealStrategy(Thread):
.....
.....
    def run_engine(self):
        self.on_start()
        self._order_handler.start()
        self._data_parser.start()
        self._data_source.start()
        self.start()

    def stop_engine(self):
        self._data_source.stop()
        self._data_parser.stop()
        self._order_handler.stop()

        self._data_source.join()
        self._data_parser.join()
        self._order_handler.join()

        self.stop()

如果要使用queue.Queue.join ,那么还必须使用queue.Queue.task_done 您可以阅读链接的文档,也可以从在线信息中看到以下内容:

队列。 task_done()

表示先前排队的任务已完成。 由队列使用者线程使用。 对于用于提取任务的每个get(),随后对task_done()的调用将告诉队列该任务的处理已完成。

如果join()当前正在阻塞,它将在所有项目都已处理完毕后恢复(这意味着已将每个put()放入队列的项目都收到了task_done()调用)。

如果调用的次数超过队列中放置的项目的次数,则引发ValueError。


队列。 加入()

阻塞直到队列中的所有项目都已获得并处理。

每当将项目添加到队列时,未完成任务的数量就会增加。 每当使用者线程调用task_done()表示已检索到该项目并且该项目的所有工作已完成时,该计数就会减少。 当未完成的任务数降至零时,join()会解除阻止。


为了测试您的问题,创建了一个示例实现来找出正在发生的情况。 它与程序的工作方式略有不同,但是演示了解决问题的方法:

#! /usr/bin/env python3
import abc
import csv
import pathlib
import queue
import sys
import threading
import time


def main():
    source_path = pathlib.Path(r'C:\path\to\file.csv')
    data_source = CSVDataSource(source_path)
    data_source.start()
    processor = StoppableThread(target=consumer, args=[data_source])
    processor.start()
    time.sleep(0.1)
    data_source.stop()


def consumer(data_source):
    while data_source.empty:
        time.sleep(0.001)
    while not data_source.empty:
        task = data_source.get_from_queue(True, 0.1)
        print(*task.data, sep=', ', flush=True)
        task.done()


class StopThread(StopIteration):
    pass


threading.SystemExit = SystemExit, StopThread


class StoppableThread(threading.Thread):
    def _bootstrap(self, stop=False):
        # noinspection PyProtectedMember
        if threading._trace_hook:
            raise RuntimeError('cannot run thread with tracing')

        def terminate():
            nonlocal stop
            stop = True

        self.__terminate = terminate

        # noinspection PyUnusedLocal
        def trace(frame, event, arg):
            if stop:
                raise StopThread

        sys.settrace(trace)
        super()._bootstrap()

    def terminate(self):
        try:
            self.__terminate()
        except AttributeError:
            raise RuntimeError('cannot terminate thread '
                               'before it is started') from None


class Queryable:
    def __init__(self, maxsize=1 << 10):
        self.__queue = queue.Queue(maxsize)

    def add_to_queue(self, item):
        self.__queue.put(item)

    def get_from_queue(self, block=True, timeout=None):
        return self.__queue.get(block, timeout)

    @property
    def empty(self):
        return self.__queue.empty()

    @property
    def full(self):
        return self.__queue.full()

    def task_done(self):
        self.__queue.task_done()

    def join_queue(self):
        self.__queue.join()


class StoppableQueryThread(StoppableThread, Queryable):
    def __init__(self, target=None, name=None, args=(), kwargs=None,
                 *, daemon=None, maxsize=1 << 10):
        super().__init__(None, target, name, args, kwargs, daemon=daemon)
        Queryable.__init__(self, maxsize)

    def stop(self):
        self.terminate()
        self.join_queue()


class DataSource(StoppableQueryThread, abc.ABC):
    @abc.abstractmethod
    def __init__(self, maxsize=1 << 10):
        super().__init__(None, 'DataSource', maxsize=maxsize)

    def run(self):
        while True:
            record = self._fetch_data()
            self.add_to_queue(record)

    @abc.abstractmethod
    def _fetch_data(self):
        pass


class CSVDataSource(DataSource):
    def __init__(self, source_path):
        super().__init__()
        self.__data_parser = self.__build_data_parser(source_path)

    @staticmethod
    def __build_data_parser(source_path):
        with source_path.open(newline='') as source:
            parser = csv.reader(source)
            next(parser, None)
            yield from parser

    def _fetch_data(self):
        try:
            return Task(next(self.__data_parser), self.task_done)
        except StopIteration:
            raise StopThread from None


class Task:
    def __init__(self, data, callback):
        self.__data = data
        self.__callback = callback

    @property
    def data(self):
        return self.__data

    def done(self):
        self.__callback()


if __name__ == '__main__':
    main()

暂无
暂无

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

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