我们知道多线程环境下,每一个线程均可以使用所属进程的全局变量。如果一个线程对全局变量进行了修改,将会影响到其他所有的线程。为了避免多个线程同时对变量进行修改,引入了线程同步机制,通过互斥锁,条件变量或者读写锁来控制对全局变量的访问。
在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
有时候使用局部变量不太方便,比如函数之间互相调用的时候,参数的传递,这时候如果使用全局变量也不行,因为每个线程处理的对象不同,因此 python 还提供了 ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的。这真是一举两得的事。
下图形象的给出了这几个变量之间的关系:
从图中可以看出进程包含线程,不同的线程可以使用同一个全局变量,但全局变量的改动也会影响到其他的线程,此时有了threadlocal变量的出现,即解决了全局变量的问题,又解决了不同线程间的局部变量改变的问题,互不影响。
全局 VS 局部变量
1、关于多线程下的全局变量的同步问题及线程锁的作用见:
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 都不同,真正做到线程之间的隔离。
参考: