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

只用全局变量并不能满足多线程环境的需求,很多时候线程还需要拥有自己的私有数据,这些数据对于其他线程来说不可见。因此线程中也可以使用局部变量,局部变量只有线程自身可以访问,同一个进程下的其他线程不可访问。

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

全局 VS 局部变量

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

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):

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 来获取本身拥有的数据。这样,就可以避免在函数调用中传递参数,如下示例:

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 实现,甚至支持协程之间的私有数据,实现更加复杂,有机会再分析。

更多阅读

本文由 selfboot 发表于 个人博客,采用署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议。

非商业转载请注明作者及出处。商业转载请联系作者本人。

本文标题为: ThreadLocal之应用篇

本文链接为: http://selfboot.cn/2016/08/22...

python中的thread_深入理解Python中的ThreadLocal变量(上)相关推荐

  1. python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递

    python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...

  2. python怎么导入包-如何理解Python中包的引入

    Python的from import *和from import *,它们的功能都是将包引入使用,但是它们是怎么执行的以及为什么使用这种语法呢? 从一模块导入全部功能 from import * me ...

  3. python赋值语句对错_深入理解Python中变量赋值的问题

    前言 在Python中变量名规则与其他大多数高级语言一样,都是受C语言影响的,另外变量名是大小写敏感的. Python是动态类型语言,也就是说不需要预先声明变量类型,变量的类型和值在赋值那一刻被初始化 ...

  4. python iterable对象_如何理解Python中的iterable对象

    转载请注明出处:https://www.jianshu.com/u/5e6f798c903a [^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法. 首先,容器和 iterable 间没有必 ...

  5. 为什么一个程序中变量只能定义一次_#带你学Python# 从简单程序出发理解Python基本语法

    欢迎回来. 通过上一篇文章,我们第一次触摸了Python,学会了如何用各种不同的方式运行Python的解释器.也介绍了很多工具和开发环境,是不是跃跃欲试了? 到这里,别的python教程就会从数据类型 ...

  6. python中的self怎么理解_python中的self理解

    前言 先介绍下类和实例 面向对象最重要的概念就是类(class)和实例(instance),类时抽象的模板,比如学生这个抽象的事物,可以用一个student类来表示.而实例时根据类创建出来的一个个具体 ...

  7. python装饰器原理-深刻理解python装饰器

    我们要完全理解python装饰器,不是很容易,主要归结有如下困难: 1. 关于函数"变量"(或"变量"函数)的理解 2. 关于高阶函数的理解 3. 关于嵌套函数 ...

  8. python语句解释_深入理解python with 语句

    深入理解python with 语句 python中with 语句作为try/finally 编码范式的一种替代, 适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的" ...

  9. python在线搭建教程_理解python web开发,轻松搭建web app!

    大家好,今天分享给大家的是理解python web开发,轻松搭建web app,希望大家学有所获! 因为 python代码的优雅美观且易于维护这一特点,越来越多的人选择使用 Python做web开发. ...

最新文章

  1. C++宏assert()
  2. MMD_2c_FrequentItemsets
  3. 半导体行业必将再火十年!两大趋势成发展新动能
  4. python中if错误-Python中常见的异常处理
  5. Keras之Mask R-CNN:《极限挑战》第四季第2期助力高考—使用Mask R-CNN代替Photoshop抠图、颜色填充框出目标检测/图像分割/语义分割
  6. CRM Order confirmation form布局
  7. ubantu 添加防火墙策略_Ubuntu 14.04 配置iptables防火墙
  8. linux——select、poll、epoll
  9. 嵌入式linux驱动笔试,嵌入式Linux驱动工程师/BSP开发工程师面试笔试题集锦
  10. 【PPT分享】特斯拉远景规划及中国供应链的机遇.pdf(附119页ppt下载链接)
  11. H.264 视频编码器的研究与分析
  12. 《位置大数据隐私管理》—— 1.5 典型的位置隐私保护技术
  13. oa系统是什么?如何使用企业oa办公系统?
  14. 【ZZULIOJ】1096: 水仙花数(函数专题)
  15. opengles特效之飘扬的旗帜
  16. 英国内政部(Home Office)间谍机构(spy powers)假装它是Ofcom咨询中的一名私人公民1514378282474...
  17. 【JavaScript】移动端扫描二维码检测浏览器(微信、支付宝、Safari、其他浏览器)
  18. 数据仓库的项目的概况
  19. UC/OS II 任务管理(4)之任务创建
  20. godaddy无法修改域名服务器,GoDaddy DNS问题导致域名解析不正常的解决办法

热门文章

  1. Vue之webpack之vue
  2. js 判断支持webgl_「WebGL基础」:第一部分
  3. Linux命令行上传本地文件到服务器 、 下载服务器文件到本地
  4. 热更新_UnityXlua热更新
  5. java 自定义注解 生成json_Java使用@JsonDeserialize注解实现自定义反序列化器
  6. kotlin 调用java_从Kotlin调用Java代码
  7. python的zip_python中的zip()函数
  8. php 字符串进行计算_PHP eval() 函数把字符串按照 PHP 代码来计算
  9. **Java有哪些悲观锁的实现_Redis 分布式锁的正确实现方式(Java 版)
  10. docker 不包含依赖 打包_2019开启新学习,带你从零了解Docker