关于多线程:Python线程和原子操作

Python threads and atomic operations

我想用同步stop()方法实现一个线程。

我见过这样的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Thread1:
    def __init__(self):
        self._stop_event = threading.Event()
        self._thread = None

    def start(self):
        self._thread = threading.Thread(target=self._run)
        self._thread.start()

    def stop(self):
        self._stop_event.set()
        self._thread.join()

    def _run(self):
        while not self._stop_event.is_set():
            self._work()

    def _work(self):
        print("working")

但我已经读到原子操作是线程安全的,在我看来,它可以在没有Event的情况下完成。所以我想到了这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Thread2:
    def __init__(self):
        self._working = False
        self._thread = None

    def start(self):
        self._working = True
        self._thread = threading.Thread(target=self._run)
        self._thread.start()

    def stop(self):
        self._working = False
        self._thread.join()

    def _run(self):
        while self._working:
            self._work()

    def _work(self):
        print("working")

它认为类似的实现在C中是不正确的,因为编译器可以将_working放入寄存器(甚至优化掉),工作线程永远不会知道变量已经改变。在Python中会发生类似的事情吗?此实现是否正确?我并不打算完全避免事件或锁,只是想了解这个原子操作。


这里有一个更全面的解决方案,如果工作线程有时需要延迟,也可以使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Worker(threading.Thread):
    quit = False

    def __init__(self, ...):
        super().__init__()
        self.cond = threading.Condition()
        ...

    def delay(self, seconds):
        deadline = time.monotonic() + seconds
        with self.cond:
            if self.quit:
                raise SystemExit()
            if time.monotinic() >= deadline:
                return
            self.cond.wait(time.monotonic() - deadline)

    def run(self):
        while not self.quit:
            # work here
            ...

            # when delay is needed
            self.delay(123)

    def terminate(self):
        with self.cond:
            self.quit = True
            self.cond.notify_all()
        self.join()

使用方法如下:

1
2
3
4
5
worker = Worker()
worker.start()
...
# finally
worker.terminate()

当然,如果您知道工作人员从不睡觉,那么您可以删除self.cond的创建和所有使用,保留其余代码。


据我所知,它在python中也是不正确的,因为_working仍然可以以其他方式放入寄存器或优化,或者可能发生其他一些会改变它的值的事情。读写这个字段可以由处理器任意重新排序。

好吧,让我们说,在多线程的世界里,你不应该真的问:为什么这不应该起作用,而应该问为什么这被保证起作用。

在大多数情况下,多线程在cpython中要容易一些,因为gil保证:

  • 在任何给定时间只执行一个解释器命令。
  • 强制线程之间的内存同步。

请记住,gil是一个实现细节,如果有人不使用它重写cpython,这可能会消失。

还要注意的是,它应该以这种方式在任何实际系统中实现它。