深刻理解Python中的ThreadLocal变量(上)

您所在的位置:网站首页 threadlocal用法 深刻理解Python中的ThreadLocal变量(上)

深刻理解Python中的ThreadLocal变量(上)

#深刻理解Python中的ThreadLocal变量(上)| 来源: 网络整理| 查看: 265

咱们知道多线程环境下,每个线程都可以使用所属进程的全局变量。若是一个线程对全局变量进行了修改,将会影响到其余全部的线程。为了不多个线程同时对变量进行修改,引入了线程同步机制,经过互斥锁,条件变量或者读写锁来控制对全局变量的访问。html

只用全局变量并不能知足多线程环境的需求,不少时候线程还须要拥有本身的私有数据,这些数据对于其余线程来讲不可见。所以线程中也可使用局部变量,局部变量只有线程自身能够访问,同一个进程下的其余线程不可访问。python

有时候使用局部变量不太方便,所以 python 还提供了 ThreadLocal 变量,它自己是一个全局变量,可是每一个线程却能够利用它来保存属于本身的私有数据,这些私有数据对其余线程也是不可见的。下图给出了线程中这几种变量的存在状况:git

线程变量

全局 VS 局部变量

首先借助一个小程序来看看多线程环境下全局变量的同步问题。github

import threading global_num = 0 def thread_cal(): global global_num for i in xrange(1000): global_num += 1 # Get 10 threads, run them and wait them all finished. threads = [] for i in range(10): threads.append(threading.Thread(target=thread_cal)) threads[i].start() for i in range(10): threads[i].join() # Value of global variable can be confused. print global_num

这里咱们建立了10个线程,每一个线程均对全局变量 global_num 进行1000次的加1操做(循环1000次加1是为了延长单个线程执行时间,使线程执行时被中断切换),当10个线程执行完毕时,全局变量的值是多少呢?答案是不肯定。简单来讲是由于 global_num += 1 并非一个原子操做,所以执行过程可能被其余线程中断,致使其余线程读到一个脏值。以两个线程执行 +1 为例,其中一个可能的执行序列以下(此状况下最后结果为1):小程序

多线程全局变量同步

多线程中使用全局变量时广泛存在这个问题,解决办法也很简单,可使用互斥锁、条件变量或者是读写锁。下面考虑用互斥锁来解决上面代码的问题,只须要在进行 +1 运算前加锁,运算完毕释放锁便可,这样就能够保证运算的原子性。多线程

l = threading.Lock() ... l.acquire() global_num += 1 l.release()

在线程中使用局部变量则不存在这个问题,由于每一个线程的局部变量不能被其余线程访问。下面咱们用10个线程分别对各自的局部变量进行1000次加1操做,每一个线程结束时打印一共执行的操做次数(每一个线程均为1000):app

def show(num): print threading.current_thread().getName(), num def thread_cal(): local_num = 0 for _ in xrange(1000): local_num += 1 show(local_num) threads = [] for i in range(10): threads.append(threading.Thread(target=thread_cal)) threads[i].start()

能够看出这里每一个线程都有本身的 local_num,各个线程之间互不干涉。函数

Thread-local 对象

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

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

global_data = {} def show(): cur_thread = threading.current_thread() print cur_thread.getName(), global_data[cur_thread] def thread_cal(): global global_data cur_thread = threading.current_thread() global_data[cur_thread] = 0 for _ in xrange(1000): global_data[cur_thread] += 1 show() # Need no local variable. Looks good. ...

保存一个全局字典,而后将线程标识符做为key,相应线程的局部数据做为 value,这种作法并不完美。首先,每一个函数在须要线程局部数据时,都须要先取得本身的线程ID,略显繁琐。更糟糕的是,这里并无真正作到线程之间数据的隔离,由于每一个线程均可以读取到全局的字典,每一个线程均可以对字典内容进行更改。

为了更好解决这个问题,python 线程库实现了 ThreadLocal 变量(不少语言都有相似的实现,好比Java)。ThreadLocal 真正作到了线程之间的数据隔离,而且使用时不须要手动获取本身的线程 ID,以下示例:

global_data = threading.local() def show(): print threading.current_thread().getName(), global_data.num def thread_cal(): global_data.num = 0 for _ in xrange(1000): global_data.num += 1 show() threads = [] ... print "Main thread: ", global_data.__dict__ # {}

上面示例中每一个线程均可以经过 global_data.num 得到本身独有的数据,而且每一个线程读取到的 global_data 都不一样,真正作到线程之间的隔离。

ThreadLocal 实现的代码量很少,可是比较难理解,涉及不少 Python 黑魔法,下篇再来分析。那么 ThreadLocal 很完美了?不!Python 的 WSGI 工具库 werkzeug 中有一个更好的 ThreadLocal 实现,甚至支持协程之间的私有数据,实现更加复杂,有机会再分析。

更多阅读

Thread local storage in Python threading – Manage concurrent threads Python线程同步机制 Linux多线程与同步 Are local variables in a python function thread safe?

本文由 selfboot 发表于 我的博客,采用署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议。非商业转载请注明做者及出处。商业转载请联系做者本人。本文标题为: ThreadLocal之应用篇本文连接为: http://selfboot.cn/2016/08/22...



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3