Генераторы Threadsafe? - PullRequest
40 голосов
/ 15 июля 2009

У меня есть многопоточная программа, в которой я создаю функцию генератора, а затем передаю ее новым потокам. Я хочу, чтобы он был общим / глобальным по своей природе, чтобы каждый поток мог получить следующее значение из генератора.

Безопасно ли использовать такой генератор, или я столкнусь с проблемами / условиями доступа к общему генератору из нескольких потоков?

Если нет, есть ли лучший способ решения проблемы? Мне нужно что-то, что будет циклически проходить по списку и генерировать следующее значение для любого потока, который его вызывает.

Ответы [ 4 ]

51 голосов
/ 15 июля 2009

Это не потокобезопасно; одновременные вызовы могут чередоваться и связываться с локальными переменными.

Общий подход заключается в использовании шаблона «ведущий-ведомый» (теперь он называется «фермер-рабочий» в ПК). Создайте третий поток, который генерирует данные, и добавьте Очередь между ведущим и ведомым, где подчиненные будут читать из очереди, а ведущий записывает в нее. Стандартный модуль очереди обеспечивает необходимую безопасность потока и организует блокировку главного устройства до тех пор, пока ведомые устройства не будут готовы прочитать дополнительные данные.

43 голосов
/ 15 июля 2009

Отредактировано для добавления бенчмарка ниже.

Вы можете обернуть генератор замком. Например,

import threading
class LockedIterator(object):
    def __init__(self, it):
        self.lock = threading.Lock()
        self.it = it.__iter__()

    def __iter__(self): return self

    def next(self):
        self.lock.acquire()
        try:
            return self.it.next()
        finally:
            self.lock.release()

gen = [x*2 for x in [1,2,3,4]]
g2 = LockedIterator(gen)
print list(g2)

В моей системе блокировка занимает 50 мс, а в очереди - 350 мс. Очередь полезна, когда у вас действительно есть очередь; например, если у вас есть входящие HTTP-запросы и вы хотите поставить их в очередь для обработки рабочими потоками. (Это не вписывается в модель итератора Python - как только у итератора заканчиваются элементы, это делается.) Если у вас действительно есть итератор, то LockedIterator - более быстрый и простой способ сделать его потокобезопасным.

from datetime import datetime
import threading
num_worker_threads = 4

class LockedIterator(object):
    def __init__(self, it):
        self.lock = threading.Lock()
        self.it = it.__iter__()

    def __iter__(self): return self

    def next(self):
        self.lock.acquire()
        try:
            return self.it.next()
        finally:
            self.lock.release()

def test_locked(it):
    it = LockedIterator(it)
    def worker():
        try:
            for i in it:
                pass
        except Exception, e:
            print e
            raise

    threads = []
    for i in range(num_worker_threads):
        t = threading.Thread(target=worker)
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

def test_queue(it):
    from Queue import Queue
    def worker():
        try:
            while True:
                item = q.get()
                q.task_done()
        except Exception, e:
            print e
            raise

    q = Queue()
    for i in range(num_worker_threads):
         t = threading.Thread(target=worker)
         t.setDaemon(True)
         t.start()

    t1 = datetime.now()

    for item in it:
        q.put(item)

    q.join()

start_time = datetime.now()
it = [x*2 for x in range(1,10000)]

test_locked(it)
#test_queue(it)
end_time = datetime.now()
took = end_time-start_time
print "took %.01f" % ((took.seconds + took.microseconds/1000000.0)*1000)
5 голосов
/ 15 июля 2009

Нет, они не поточнобезопасны. Вы можете найти интересную информацию о генераторах и многопоточности в:

http://www.dabeaz.com/generators/Generators.pdf

0 голосов
/ 15 июля 2009

Это зависит от того, какую реализацию Python вы используете. В CPython GIL делает все операции над объектами Python поточно-безопасными, поскольку только один поток может выполнять код в любой момент времени.

http://en.wikipedia.org/wiki/Global_Interpreter_Lock

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...