2017-02-22 22 views
0

Я испытываю странное поведение в Python. Когда я использовал лямбду в качестве цели потока, поведение было непоследовательным.Python: использование лямбда в качестве цели для нитей вызывает странное поведение

Первый пример, как это:

import time,threading 

locker= threading.RLock() 

def func(obj): 
    while obj['count']>0: 
    with locker: print 'thread',obj,id(obj) 
    obj['count']-= 1 
    time.sleep(0.1) 
    with locker: print 'finished',obj,id(obj) 

def make_thread1(): 
    threads= [] 
    objs= {} 
    for i in range(2): 
    objs[i]= {} 
    objs[i]['id']= i 
    objs[i]['count']= (i+2)*2 
    t= threading.Thread(name='func'+str(i), target=lambda: func(objs[i])) 
    t.start() 
    threads.append(t) 
    return threads,objs 

if __name__=='__main__': 
    threads,objs= make_thread1() 
    for t in threads: 
    t.join() 

Существовали две модели результатов. Один из них:

thread {'count': 4, 'id': 0} 139911658041704 
thread {'count': 6, 'id': 1} 139911658041984 
thread {'count': 3, 'id': 0} 139911658041704 
thread {'count': 5, 'id': 1} 139911658041984 
thread {'count': 4, 'id': 1} 139911658041984 
thread {'count': 2, 'id': 0} 139911658041704 
thread {'count': 3, 'id': 1} 139911658041984 
thread {'count': 1, 'id': 0} 139911658041704 
thread {'count': 2, 'id': 1} 139911658041984 
finished {'count': 0, 'id': 0} 139911658041704 
thread {'count': 1, 'id': 1} 139911658041984 
finished {'count': 0, 'id': 1} 139911658041984 

Это результат, которого я ожидал. Однако при выполнении этого кода в несколько раз, иногда это приводило так:

thread {'count': 6, 'id': 1} 140389870428800 
thread {'count': 5, 'id': 1} 140389870428800 
thread {'count': 4, 'id': 1} 140389870428800 
thread {'count': 3, 'id': 1} 140389870428800 
thread {'count': 2, 'id': 1} 140389870428800 
thread {'count': 1, 'id': 1} 140389870428800 
finished {'count': 0, 'id': 1} 140389870428800 
finished {'count': 0, 'id': 1} 140389870428800 

При создании нити, lambda:func(objs[0]) и lambda:func(objs[1]) были определены как целевые функции соответственно, но на самом деле обе целевые функции были lambda:func(objs[1]) (но разные случаи).

Я не могу понять, почему это произошло.

Ну, одна из возможностей заключалась бы в том, что я использовал локальную переменную i при создании лямбда-функций. Но он должен быть оценен при выполнении t.start() ...? Тогда почему были два шаблона результатов?

Для более расследования, я изменил код без лямбда:

class TObj: 
    def __init__(self): 
    self.objs= None 
    def f(self): 
    func(self.objs) 

def make_thread2(): 
    threads= [] 
    classes= {} 
    for i in range(2): 
    classes[i]= TObj() 
    classes[i].objs= {} 
    classes[i].objs['id']= i 
    classes[i].objs['count']= (i+2)*2 
    t= threading.Thread(name='func'+str(i), target=classes[i].f) 
    t.start() 
    threads.append(t) 
    return threads,classes 

if __name__=='__main__': 
    threads,classes= make_thread2() 
    for t in threads: 
    t.join() 

Этот код работает отлично:

thread {'count': 4, 'id': 0} 140522771444352 
thread {'count': 6, 'id': 1} 140522771445472 
thread {'count': 3, 'id': 0} 140522771444352 
thread {'count': 5, 'id': 1} 140522771445472 
thread {'count': 2, 'id': 0} 140522771444352 
thread {'count': 4, 'id': 1} 140522771445472 
thread {'count': 1, 'id': 0} 140522771444352 
thread {'count': 3, 'id': 1} 140522771445472 
finished {'count': 0, 'id': 0} 140522771444352 
thread {'count': 2, 'id': 1} 140522771445472 
thread {'count': 1, 'id': 1} 140522771445472 
finished {'count': 0, 'id': 1} 140522771445472 

Я хочу, чтобы понять причины, почему несоответствие первого кода произошло.

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

ответ

0

Идея использовать лямбду с переменным циклом описана в Deferred evaluation with lambda in Python

Мы можем использовать петлю (локальный) переменные в лямбда, используя значение аргументов по умолчанию. В моем случае создание лямбда-функции, например:

t= threading.Thread(name='func'+str(i), target=lambda i=i: func(objs[i])) 

Это работало так же, как и второе решение, в котором используется объект класса.

О другой точке: когда Thread.start оценивает цель? Это будет зависеть от системы. Это может измениться. Если была задержка, она была бы оценена после того, как i было увеличено. Это было бы причиной несогласованности.