Могут ли потоки python обращаться к переменным в пространстве имен?

У меня есть script, который создает кучу потоков, запускает программу для использования потоков для запуска задач из очереди и возвращает что-то из каждого потока. Я хочу подсчитать, сколько из них успешно возвращено, поэтому я устанавливаю переменную "success = 0" и увеличиваю ее каждый раз, когда очередь сообщает о завершении задачи.

Тем не менее, я получаю "UnboundLocalError: локальная переменная" успешно ", на которую ссылаются перед назначением"

Что происходит?

Вот пример кода:

successful = 0

q = Queue(200)
for i in range(100):
    t=Thread(target=foo)
    t.daemon=True
    t.start()
def foo():
    while True:
        task=q.get()
        #do some work
        print task
        successful+=1 # triggers an error
        q.task_done()
for i in range(100):
    q.put("Foo")
q.join()
print successful

Ответ 1

successful+=1

не является потокобезопасной. Если несколько потоков пытаются увеличить общую глобальную переменную, могут возникнуть столкновения, а successful не будет увеличиваться правильно.

Чтобы избежать этой ошибки, используйте блокировку:

lock = threading.Lock()
def foo():
    global successful
    while True:
        ...
        with lock:
            successful+=1 

Вот какой код, чтобы продемонстрировать, что x + = 1 не является потокобезопасным:

import threading
lock = threading.Lock()
x = 0
def foo():
   global x
   for i in xrange(1000000):
       # with lock:    # Uncomment this to get the right answer
            x += 1
threads = [threading.Thread(target=foo), threading.Thread(target=foo)]
for t in threads:
    t.daemon = True    
    t.start()
for t in threads:
    t.join()

print(x)

дает:

% test.py 
1539065
% test.py 
1436487

Эти результаты не согласуются и меньше ожидаемого 2000000. Раскомментирование блокировки дает правильный результат.

Ответ 2

Проблема возникает из-за того, что переменная, назначенная внутри функции, считается локальной для этой функции. Если вы хотите изменить значение переменной successfull, которая создается за пределами foo, вам нужно явно сообщить интерпретатору, что вы собираетесь работать с глобальной переменной внутри функции. Это можно сделать следующим образом:

def foo():
    global successfull
    while True:
        task=q.get()
        #do some work
        print task
        successful+=1 # triggers an error
        q.task_done()

Теперь код должен работать как ожидалось.