Python中的ThreadLocal变量

来源:互联网 发布:黑马python视频教程 编辑:程序博客网 时间:2024/05/21 19:34

我们知道多线程环境下,每一个线程均可以使用所属进程的全局变量。如果一个线程对全局变量进行了修改,将会影响到其他所有的线程。为了避免多个线程同时对变量进行修改,引入了线程同步机制,通过互斥锁,条件变量或者读写锁来控制对全局变量的访问。

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

有时候使用局部变量不太方便,比如函数之间互相调用的时候,参数的传递,这时候如果使用全局变量也不行,因为每个线程处理的对象不同,因此 python 还提供了 ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的。这真是一举两得的事。

下图形象的给出了这几个变量之间的关系:
这里写图片描述

从图中可以看出进程包含线程,不同的线程可以使用同一个全局变量,但全局变量的改动也会影响到其他的线程,此时有了threadlocal变量的出现,即解决了全局变量的问题,又解决了不同线程间的局部变量改变的问题,互不影响。

全局 VS 局部变量

1、关于多线程下的全局变量的同步问题及线程锁的作用见:

多线程和Lock

2、在线程中使用局部变量则不存在这个问题,因为每个线程的局部变量不能被其他线程访问。

# coding=utf-8import threading,timedef show(num):    print ('i am %s num=%s \n'% (threading.current_thread().getName(), num))def thread_cal():    local_num = 0 # 局部变量    for _ in range(5):        #time.sleep(2)        local_num += 1        show(local_num)threads = []for i in range(5):    threads.append(threading.Thread(target=thread_cal))    threads[i].start()for i in range(5):    threads[i].join()  # 阻碍主进程print('main process end!')

运行结果:

i am Thread-1 num=1 i am Thread-2 num=1 i am Thread-5 num=1 i am Thread-3 num=1 i am Thread-4 num=1 i am Thread-1 num=2 i am Thread-2 num=2 i am Thread-5 num=2 i am Thread-3 num=2 i am Thread-4 num=2 ......i am Thread-1 num=5 i am Thread-2 num=5 i am Thread-5 num=5 i am Thread-3 num=5 i am Thread-4 num=5 main process end!

可以看出这里每个线程都有自己的 local_num,各个线程之间互不干涉。

上面程序中我们需要给 show 函数传递 local_num 局部变量,并没有什么不妥。不过考虑在实际生产环境中,我们可能会调用很多函数,每个函数都需要很多局部变量,这时候用传递参数的方法会很不友好。

为了解决这个问题,一个直观的的方法就是建立一个全局字典,保存进程 ID 到该进程局部变量的映射关系,运行中的线程可以根据自己的 ID 来获取本身拥有的数据。这样,就可以避免在函数调用中传递参数,如下示例:

# coding=utf-8import threading,timeglobal_data = {}def show():    cur_thread = threading.current_thread()    print ('i am %s num=%s \n'% (cur_thread.getName(), global_data[cur_thread]))def thread_cal():    global global_data    cur_thread = threading.current_thread()    global_data[cur_thread] = 0    for i in range(5):        #time.sleep(2)        global_data[cur_thread] += 1        show()threads = []for i in range(5):    threads.append(threading.Thread(target=thread_cal))    threads[i].start()for i in range(5):    threads[i].join()  # 阻碍主进程print('main process end!')

实验结果同上个实验结果

保存一个全局字典,然后将线程标识符作为key,相应线程的局部数据作为 value,这种做法略显繁琐。而且这里并没有真正做到线程之间数据的隔离,因为每个线程都可以读取到全局的字典,每个线程都可以对字典内容进行更改。

为了更好解决这个问题,python 线程库实现了 ThreadLocal 变量(很多语言都有类似的实现,比如Java)。ThreadLocal 真正做到了线程之间的数据隔离,并且使用时不需要手动获取自己的线程 ID,如下示例:

global_data=threading.local()def show():    cur_thread=threading.current_thread()     print ('i am %s num=%s \n' % (cur_thread.getName() ,                                  global_data.num))def thread_cal():    global_data.num=0    #cur_thread=threading.current_thread()    #global_data[cur_thread]=0    for i in range(5):        global_data.num+=1               time.sleep(1)        show()threads = []for i in range(5):    threads.append(threading.Thread(target=thread_cal))    threads[i].start()for i in range(5):    threads[i].join()  # 阻碍主进程print('main thread:',global_data.__dict__) # {}

实验结果:

i am Thread-1 num=1 i am Thread-2 num=1 i am Thread-3 num=1 i am Thread-4 num=1 i am Thread-5 num=1 i am Thread-1 num=2 i am Thread-2 num=2 i am Thread-3 num=2 i am Thread-4 num=2 i am Thread-5 num=2 i am Thread-1 num=3 i am Thread-2 num=3 i am Thread-3 num=3 i am Thread-4 num=3 i am Thread-5 num=3 i am Thread-1 num=4 i am Thread-2 num=4 i am Thread-3 num=4 i am Thread-4 num=4 i am Thread-5 num=4 i am Thread-1 num=5 i am Thread-2 num=5 i am Thread-3 num=5 i am Thread-4 num=5 i am Thread-5 num=5 main thread: {}

上面示例中每个线程都可以通过 global_data.num 获得自己独有的数据,并且每个线程读取到的 global_data 都不同,真正做到线程之间的隔离。

参考:

http://python.jobbole.com/86150/