Mutex适合快速单作者/慢读者(cpython)?

时间:2012-11-06 18:05:11

标签: python multithreading mutex shared-memory

在我的应用程序中,我有一个单独的线程,它在日志行上执行非常快速的处理以产生浮点值。通常只有一个其他线程以一定的间隔对值执行慢速读取。每隔一段时间,其他线程就可以来来去去,并对这些值进行一次性读取。

我的问题是关于互斥量(在cpython中)的必要性,对于这种特定情况,数据只是最新的数据。它不是必须与其他任何东西同步的关键值(甚至是同时写入的其他字段)。只是......它的价值是什么。

话虽这么说,我知道我可以轻松添加一个锁(或读取器/写锁)来保护值的更新,但我想知道获取/释放的开销是否快速连续的过程中整个日志(比如平均5000行)不值得它“恰当地”共享资源。

根据What kinds of global value mutation are thread-safe?上的文档,这些分配应该是原子操作。

以下是逻辑的基本示例:

import time
from random import random, choice, randint
from threading import Thread 

class DataStructure(object):
    def __init__(self):
        self.f_val = 0.0
        self.s_val = ""

def slow_reader(data):
    """ 
    Loop much more slowly and read values 
    anywhere between 1 - 5 second intervals
    """
    for _ in xrange(10):

        f_val = data.f_val 
        # don't care about sync here
        s_val = data.s_val

        print f_val, s_val

        # in real code could be even 30 or 60 seconds
        time.sleep(randint(1,3))

def fast_writer(data):
    """ Update data extremely often """
    for _ in xrange(20000):
        f_val, s_val = do_work()

        data.f_val = f_val
        # don't care about sync here
        data.s_val = s_val 


FLOAT_SRC = [random()*100 for _ in xrange(100)]
STR_SRC = ['foo', 'bar', 'biz', 'baz']

def do_work():
    time.sleep(0.001)
    return choice(FLOAT_SRC), choice(STR_SRC)


if __name__ == "__main__":

    data = DataStructure()

    threads = [
        Thread(target=slow_reader, args=(data,)),
        Thread(target=fast_writer, args=(data,)),
    ]

    for t in threads:
        t.daemon=True
        t.start()

    for t in threads:
        t.join()

这表示快速日志解析器(实际上是通过PIPE读取)在每一行上工作,而慢速定期读取器抓取当时的当前值。在任何时候,另一个曾经读过的线程都可以从数据结构中获取相同的值。

这是不是根本不需要cpython中的互斥锁的情况吗?

修改

为了澄清一点......我甚至不需要浮点数和字符串字段与上次写入同步。如果调度程序决定在float和字符串读取之间切换上下文,则可以。我只是想知道我是否需要锁的开销来简单地读取任何时刻分配的任何值。

我担心的是,编写器将以非常快的速度进行循环,锁定和解锁通常无法争用的锁。

有效地假设这是我在reader

中所关注的全部内容
def slow_reader(data):
    for _ in xrange(10):
        f_val = data.f_val 
        print f_val
        time.sleep(randint(1,3))

2 个答案:

答案 0 :(得分:2)

进行并发访问时需要一个互斥锁:

  • 关于复合值,其中一个访问必须以原子方式修改多个点中的值;
  • 关于简单值,并且至少有两个访问正在编写。

在您的示例中,值为复合(2个字段),并且修改在多个位置(这两个字段)上运行,因此您应该放置一个互斥锁以确保读取器不会在两个修改之间进行调度

编辑:如果读者不关心同步的字段,那么您不需要互斥锁。

答案 1 :(得分:2)

在获取单个现有项目时,您应该将容器锁定在阅读器中,但如果项目本身不再被任何内容修改而且不会被移动,则只要阅读器有项目,就可以释放互斥锁。

如果可以修改项目,您可以获得快速复制和释放互斥锁,或者为单个项目分别使用互斥锁,因此其他容器可以由其他人处理。你的情况听起来好像不需要担心这个。

如果您有许多读者应该选择最旧的未处理项目,那么您需要一个队列(可能就像最新采用项目的索引一样简单)和一个单独的互斥锁。这甚至可能是一个原子整数,因此您可以避免为“队列”完全需要互斥锁。

实际上,通过适当的原子整数排列和轮询,您可以完全避免使用互斥锁。最新完整项目的原子整数索引,由编写者增加,仅由轮询读者读取。最新采集项目的第二个原子整数索引,由读者增加,然后开始等待该索引准备就绪(如果尚未准备好)。

(某些通知​​机制可以避免读者轮询,但这些需要一个互斥锁或套接字,两者都非常昂贵)。