Python线程/多处理不需要Mutex?

时间:2015-10-30 11:42:32

标签: python multithreading concurrency

对于类似管理员的项目,我使用线程库进行管理 一些孩子的过程。在某些时候,用户可以提示命令发送 进程管理线程的说明。这些命令存储在 主进程和进程管理线程之间共享的Queue对象。 我以为我需要互斥来解决并发问题所以我做了一点 尝试它的脚本,但没有互斥,首先要确保我得到预期的 并发问题。

我希望从脚本中每秒打印一个凌乱的int列表:

import threading
import time

def longer(l, mutex=None):
    while 1:
        last_val = l[-1]
        l.append(last_val + 1)
        time.sleep(1)
    return

dalist = [0]
t = threading.Thread(target=longer, args=(dalist,))
t.daemon = True
t.start()

while 1:
    last_val = dalist[-1]
    dalist.append(last_val + 1)
    print dalist
    time.sleep(1)

但实际上它打印了一个很好的以下int列表:

[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4, 5, 6]

从另一篇文章的this回答我认为它来自线程库,所以我对多处理库做了同样的事情:

import multiprocessing as mp
import time

def longer(l, mutex=None):
    while 1:
        last_val = l[-1]
        l.append(last_val + 1)
        time.sleep(1)
    return

dalist = [0]
t = mp.Process(target=longer, args=(dalist,))
t.start()

while 1:
    last_val = dalist[-1]
    dalist.append(last_val + 1)
    print dalist
    time.sleep(1)

但是我得到了相同的结果,有点“慢”' :

[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]

所以我想我真的需要互斥来管理类似Queue的对象 线程之间共享??? 而且,从上面的代码之一,我怎么能有效地再现 我搜索的预期并发问题?

感谢您阅读

编辑1:user4815162342的评论中我改变了第一个片段,并通过在“更长”的内部移动睡眠呼叫来设置某种竞争条件。值检索和列表追加之间的函数:

import threading
import time

def longer(l, mutex=None):
    while 1:
        last_val = l[-1]
        time.sleep(1)
        l.append(last_val + 1)
    return 

dalist = [0]
t = threading.Thread(target=longer, args=(dalist,))
t.daemon = True
t.start()

while 1:
    last_val = dalist[-1]
    dalist.append(last_val + 1)
    print dalist
    time.sleep(1)

给我这样的东西:

[0, 1]
[0, 1, 1, 2]
[0, 1, 1, 2, 2, 3]
[0, 1, 1, 2, 2, 3, 3, 4]

我设法使用线程锁解决我的人为问题:

import threading
import time

def longer(l, mutex=None):
    while 1:
        if mutex is not None:
            mutex.acquire()
        last_val = l[-1]
        time.sleep(1)
        l.append(last_val + 1)
        if mutex is not None:
            mutex.release()
    return

dalist = [0]
mutex = threading.Lock()
t = threading.Thread(target=longer, args=(dalist, mutex))
t.daemon = True
t.start()

while 1:
    if mutex is not None:
        mutex.acquire()
    last_val = dalist[-1]
    dalist.append(last_val + 1)
    if mutex is not None:
        mutex.release()
    print dalist
    time.sleep(1)

然后产生:

[0, 1, 2]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1 个答案:

答案 0 :(得分:2)

您的第一个代码段包含竞争条件,但确实需要互斥锁。全局解释器锁使竞争条件很少,因为一个线程在任何给定时间运行。但是,当前线程每隔几个字节码指令放弃全局解释器锁的所有权,以便为其他线程提供运行机会。所以,鉴于你的代码:

last_val = dalist[-1]
dalist.append(last_val + 1)

如果字节码切换在执行第一行后发生,则另一个线程将拾取相同的last_val并将其附加到列表中。将控制权交还给初始线程后,last_val中存储的值将第二次附加到列表中。一个互斥体会以明显的方式阻止竞争:列表访问和追加之间的上下文切换将控制另一个线程,但它会立即被阻塞在互斥锁中并放弃控制回原始线程。

你的第二个例子"工作"因为这两个进程有单独的列表实例。修改一个列表并不会影响另一个列表,因此其他进程也可能无法运行。虽然multiprocessing具有threading的替代API,但基本概念存在巨大差异,需要在从一个切换到另一个时考虑到这一点。