Python中的ThreadLocal变量

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

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

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

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

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

全局 VS 局部变量

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

多线程和Lock

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

# coding=utf-8
import threading,time
def 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-8
import threading,time

global_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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值