多线程资源访问 - 我应该在哪里放置我的锁?

5
我有一个多线程的代码,每个线程都需要写入同一个文件。为了避免并发问题,我正在使用Lock对象。
我的问题是,我是否正确地使用了Lock。如果我在每个线程内部设置锁,那么这个锁是全局的还是只针对特定线程的呢?
基本上,我应该先创建一个锁并将其引用传递给每个线程,还是像我在这里做的那样在线程内部进行设置?
import time
from threading import Thread, Lock

def main():
    for i in range(20):
        agent = Agent(i)
        agent.start()

class Agent(Thread):
    def __init__(self, thread_num):
        Thread.__init__(self)
        self.thread_num = thread_num

    def run(self):
        while True:
            print 'hello from thread %s' % self.thread_num
            self.write_result()   

    def write_result(self):
        lock = Lock()
        lock.acquire()
        try:
            f = open('foo.txt', 'a')
            f.write('hello from thread %s\n' % self.thread_num)
            f.flush()
            f.close()
        finally:
            lock.release()

if __name__ == '__main__':
    main()
7个回答

6

针对您的使用情况,一种方法是编写一个锁定的file子类:

class LockedWrite(file):
    """ Wrapper class to a file object that locks writes """
    def __init__(self, *args, **kwds):
        super(LockedWrite, self).__init__(*args, **kwds)
        self._lock = Lock()

    def write(self, *args, **kwds):
        self._lock.acquire()
        try:
            super(LockedWrite, self).write(*args, **kwds)
        finally:
            self._lock.release()

要在您的代码中使用,只需替换以下函数:

def main():
    f = LockedWrite('foo.txt', 'a')

    for i in range(20):
        agent = Agent(i, f)
        agent.start()

class Agent(Thread):
    def __init__(self, thread_num, fileobj):
        Thread.__init__(self)
        self.thread_num = thread_num
        self._file = fileobj    

#   ...

    def write_result(self):
        self._file.write('hello from thread %s\n' % self.thread_num)

这种方法将文件锁定放在文件本身中,我认为这更加清晰。


除了无意义的“filelock = Lock()”部分外,我更喜欢这个解决方案而不是我的。如果它能够拦截其他试图以写入/追加方式打开foo.txt的尝试并返回原始的LockedWrite对象,那就更好了。 - Joseph Bui

3
在方法外创建锁。
class Agent(Thread):
    mylock = Lock()
    def write_result(self):
        self.mylock.acquire()
        try:
            ...
        finally:
            self.mylock.release()

如果使用的是 Python 版本大于等于 2.5:

class Agent(Thread):
    mylock = Lock()
    def write_result(self):
        with self.mylock:
            ...

要在Python 2.5中使用该功能,您必须从未来导入该语句:

from __future__ import with_statement

是的,您将其移出了方法,但仍在线程本身中创建它。这不是一个问题吗? - Corey Goldberg
@cgoldberd:它被创建为class属性,这意味着一个单一的属性将被为所有线程所创建。这是一个更好的存放位置,因为所有东西都保留在线程类中。 - nosklo
抓住了。我喜欢那种方法。 - Corey Goldberg

1

lock() 方法每次调用都返回一个锁对象。因此,每个线程(实际上是每个 write_result 调用)都将拥有不同的锁对象。这样就不会出现锁定问题。


1

所使用的锁需要对所有线程都是共同的,或者至少确保两个锁不能同时锁定同一资源。


1

锁实例应该与文件实例相关联。

换句话说,您应该同时创建锁和文件,并将它们都传递给每个线程。


1

您可以简化一些事情(以稍微增加开销为代价),通过指定一个单独的线程(可能专门为此创建)作为唯一写入文件的线程,并让所有其他线程通过将要添加到文件中的字符串放置到queue.Queue对象中委托给文件编写器。

队列具有所有锁定内置,因此任何线程都可以安全地在任何时候调用Queue.put()。文件编写器将是唯一调用Queue.get()的线程,并且可以假定大部分时间都在该调用上阻塞(使用合理的超时来允许线程对关闭请求进行干净的响应)。所有同步问题都将由队列处理,您将被免除担心是否忘记了某个锁定获取/释放... :)


我通常使用队列和单个写入器来完成这些工作。我只是需要一些关于锁的澄清。 - Corey Goldberg

0
我非常确定每个线程的锁需要是相同的对象。请尝试以下代码:
import time
from threading import Thread, Lock

def main():
    lock = Lock()
    for i in range(20):
        agent = Agent(i, lock)
        agent.start()

class Agent(Thread, Lock):
    def __init__(self, thread_num, lock):
        Thread.__init__(self)
        self.thread_num = thread_num
        self.lock = lock

    def run(self):
        while True:
            print 'hello from thread %s' % self.thread_num
            self.write_result()   

    def write_result(self):
        self.lock.acquire()
        try:
            f = open('foo.txt', 'a')
            f.write('hello from thread %s\n' % self.thread_num)
            f.flush()
            f.close()
        finally:
            lock.release()

if __name__ == '__main__':
    main()

为什么那行不通?先创建锁,然后将其传递到每个线程中。 - Corey Goldberg
抱歉,我忘记将锁对象实际传递给代理构造函数。已修正。 - Joseph Bui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接