Pra*_*tic 5 python cpython python-multithreading thread-synchronization python-asyncio
异步文档阅读:
大多数异步对象不是线程安全的。您仅应担心在事件循环之外访问对象。
有人可以解释一下还是举一个例子说明如何滥用asyncio导致线程之间共享的对象不同步写入?我认为GIL意味着一次只能有一个线程可以运行解释器,因此解释器中发生的事件(例如读取和写入Python对象)在线程之间是微不足道的同步。
上面引用的第二句话听起来像个线索,但我不确定该怎么做。
我猜想线程可能总是会因释放GIL并决定以任何方式写入Python对象而造成严重破坏,但这不是特定于asyncio的,因此我认为文档不是这里所指的。
这是否可能是异步PEP保留某些异步对象不是线程安全的选项的问题,即使目前在CPython中的实现恰好是线程安全的呢?
实际上,不,每个线程就是解释器的一个新线程。
它是由操作系统管理的真正线程,而不是仅用于 Python 虚拟机中的 Python 代码的内部管理线程。
需要 GIL 来防止基于操作系统的线程搞乱 Python 对象。
想象一个线程在一个 CPU 上,另一个线程在另一个 CPU 上。纯并行线程,用汇编编写。两者同时尝试更改注册表值。根本不理想的情况。访问相同内存位置的汇编指令最终会导致何时何地移动什么内容。最终这种动作的结果很容易导致segmentation fault。好吧,如果我们用 C 编写,C 控制那部分,所以这不会发生在 C 代码中。GIL 在 C 级别对 Python 代码执行相同的操作。因此,实现 Python 对象的代码在更改它们时不会失去其原子性。想象一下,一个线程将一个值插入到一个列表中,而这个列表在另一个线程中被下移了,因为另一个线程从中删除了一些元素。如果没有 GIL,这将崩溃。
GIL 对线程内代码的原子性没有任何作用。它仅用于内部内存管理。
即使您有像 deque() 这样的线程安全对象,如果您一次对其执行多个操作,而没有额外的锁,您也可以从插入到两者之间的另一个线程中获取结果。哎呀,问题出现了!
假设一个线程从堆栈中获取一个对象,检查它的某些内容,如果条件正确则将其删除。
stack = [2,3,4,5,6,7,8]
def thread1 ():
while 1:
v = stack[0]
sleep(0.001)
if v%2==0: del stack[0]
sleep(0.001)
Run Code Online (Sandbox Code Playgroud)
当然,这是愚蠢的,应该使用 stack.pop(0) 来避免这种情况。但这是一个例子。
让另一个线程每 0.002 秒添加一次:
def thread2 ():
while 1:
stack.insert(0, stack[-1]+1)
sleep(0.002)
Run Code Online (Sandbox Code Playgroud)
现在如果你这样做:
thread(thread2,())
sleep(1)
thread(thread1,())
Run Code Online (Sandbox Code Playgroud)
会有一段时间,虽然不太可能,thread2() 试图在 thread1() 的检索和删除之间准确地堆叠新项目。因此,thread1() 将删除新添加的项目而不是被检查的项目。结果不符合我们的意愿。因此,GIL 不控制我们在线程中做什么,而是控制线程在更基本的意义上对彼此做什么。
想象一下,您编写了一个服务器来购买某些活动的门票。两个用户连接并尝试同时购买同一张票。如果您不小心,用户可能会最终坐在另一个之上。
线程安全对象是执行操作的对象,并且在第一个操作完成之前不允许另一个操作发生。
例如,如果您在一个线程中迭代 deque(),并且在其中另一个线程尝试追加某些内容,则 append() 将阻塞,直到第一个线程完成对它的迭代。这是线程安全的。