threading.local()、多线程里全局变量锁
这个人的系列文章值得一读:http://blog.51cto.com/suhaozhi/category3.html/p2,不过这个系列总共15偏,Python并发入门,有很多文字描述错误,有些道理也是错的,特别是进程那块,竟然说和线程等同,筛选着看就行
你需要对多线程程序中的临界区加锁以避免竞争条件。
要在多线程程序中安全使用可变对象,你需要使用 threading 库中的 Lock
对象,就像下边这个例子这样:
import threading class SharedCounter:
'''
A counter object that can be shared by multiple threads.
'''
def __init__(self, initial_value = ):
self._value = initial_value
self._value_lock = threading.Lock() def incr(self,delta=):
'''
Increment the counter with locking
'''
with self._value_lock:
self._value += delta def decr(self,delta=):
'''
Decrement the counter with locking
'''
with self._value_lock:
self._value -= delta
Lock
对象和 with
语句块一起使用可以保证互斥执行,就是每次只有一个线程可以执行 with 语句包含的代码块。with 语句会在这个代码块执行前自动获取锁,在执行结束后自动释放锁。
多线程与互斥锁
一、锁的概念。
锁,通常被用来实现共享数据的访问,为每一个共享的数据,创建一个Lock对象(一把锁),当需要访问这个共享的资源时,可以调用acquire方法来获取一个锁的对象,当共享资源访问结束后,在调用release方法去解锁;最好结合with使用,因为with可以自动释放锁,也会处理异常导致释放锁失败的问题。
二、python中的互斥锁。
在介绍互斥锁之前,先来一起看一个例子。(每个线程对num实现一次-1的操作)
import threading
import time num = #每个线程都共享这个变量。
tread_list = [] def add_num():
global num #每个线程都去获取这个全局变量。
temp_num = num
time.sleep(0.1) #执行sleep,相当于执行IO操作.
num = temp_num - #对公共的变量做一个-1的操作。
for i in range(): #同时开启200个线程
t = threading.Thread(target=add_num)
t.start() # 子线程准备就绪,等待获取CPU时间片执行线程任务add_num
tread_list.append(t)
for t in tread_list:
t.join() # 表示主线程会等待所有子线程执行结束后继续往下执行下面的代码语句,即执行:print "ending....num = %s" %(num)
print "ending....num = %s" %(num)
最后的结果就是:ending....num = 199
结果并不是我们想要的。来分析下为何会出现这种现象。
200个线程现在想统一修改一个全局变量,由于python解释器的GIL(全局解释锁)锁的限制,每次只能有一个线程在cpu上运行,在执行到sleep时,就相当于一次I/O操作,这时就会切到其他的线程,在执行sleep之前,当前运行的这个线程,这个线程取到的全局变量的值是200(temp_num = 200),还没来得及做修改,就被切换到其他线程了,其他的线程也是一样的道理,取到temp_num = 200这个值后,还没来得及计算,执行到sleep触发一次IO操作后,又切到了其他的线程,第2个第3个直到最后一个线程都拿到了temp_num=200这个变量后,后面的计算操作才会开始运行!(不要忘记一个概念,线程在切换之前,是会保存当前执行状态的)当所有线程都拿到了emp_num=200这个变量后,每个线程都会自己执行一遍
num = temp_num - 1这也就导致了每个线程执行的都是200-1 所以,最后的结果就等于199.
下面执行的结果却是200,为什么?
因为主线程没有使用join,所以主线程没有等待子线程结束就先结束了,这个时候,所有的子线程还没有来得及做减一操作,所以num的值还是200。
此时的200个子线程就成了孤儿线程,会有系统的初始进程init进行托管,等子线程执行结束后,init把子线程的资源进行回收。
最后我们再来查看num的值,依然是199,这是因为子线程被遗弃后成为孤儿线程,但是依然长大成人,完成了自己的任务,然后被init回收。
还拿刚刚写的那个减法程序举例,我们把sleep的时间缩短为0.001秒看看会出现什么效果?还是上一段代码,只不过把add_num函数的time.sleep(0.1)改为time.sleep(0.001)看看出现了什么效果。这个结果完全出乎意料,最终num变成了115或者其他的值,每个系统值会有不同。
接着来分析下造成这种结果的原因。当sleep时间较短的时候,在线程切换的过程中,之前运行的线程的sleep就已经执行结束了,就会重新参与竞争cpu资源,在切的过程中,之前的线程sleep结束,就有了被切回去的可能,继续执行后面的num = temp_num - 1 所以就会导致这种情况。
注意!!这里面的sleep是用来模拟程序中的I/O操作!
从第二个例子中,我们可以看到一个全局资源被抢占的现象,没有控制多个线程对一个全局资源的访问控制,造成全局资源的损坏(这里的损坏是指得到了我们不想要的结果)使我们无法预测程序最后执行的结果,如果想避免这种问题,就需要用到“互斥锁”。
“互斥锁”最主要的作用就是,保证在操作共享数据时,共享数据的完整性。
互斥锁实现的方式,就是为每个共享的资源创建一个Lock对象,当需要访问这个共享资源的时候,调用这个锁的acquire方法来获取锁的对象,资源访问结束后,在调用release方法去解锁。
我们对上面的程序进行整改,为此我们需要添加一个互斥锁变量t_lock = threading.Lock(),然后在争夺资源的时候之前我们会先抢占这把锁t_lock.acquire(),对资源使用完成之后我们在释放这把锁t_lock.release().
# -*- coding:utf- -*- import threading
import time num =
tread_list = [] t_lock = threading.RLock() #创建一个锁的对象。 或者写成:t_lock = threading.Lock() def add_num(): global num,temp_num if t_lock.acquire(): #加锁 使用with: with t_lock:
temp_num = num temp_num=num
time.sleep(0.001) #执行sleep,相当于执行IO操作. time.sleep(0.001)
num = temp_num - 1 num = temp_num -1
t_lock.release() #公共资源访问和操作结束后,解锁。 for i in range():
t = threading.Thread(target=add_num)
t.start()
tread_list.append(t)
for t in tread_list:
t.join() print "ending....num = %s" %(num)
最后看下输出结果:ending....num = 800。之前的资源抢占现象得到了解决。
当一个线程去调用一个Lock对象的acquire()方法去得到一个锁时,这把锁就会进入一个“locked”锁定的状态,在锁定时,每次只有一个线程可以获得锁,如果有第二个线程试图去获得锁(去访问操作共享资源时),去操作共享的数据时,第二个线程就会进入阻塞状态,直到线程1对共享数据资源操作结束后,调用了这个lock对象的release方法后(此时的锁已经变为“unlocked”状态),线程二才可以去操作共享资源。
大概的加锁思路就是这样的:
import threading R=threading.Lock() #创建一个锁的对象 R.acquire() #加锁 ''' 对公共数据的操作 #执行了对公共数据的操作后 ''' R.release() #解锁
最后补充~
写到这里,可能会有人觉得,互斥锁和join没什么区别!!事实并非如此!
互斥锁可以做到,只有对公共数据进行访问或者操作的时候是串行模式!
如果使用了join,那么两个线程中所有执行的操作都会变为串行模式!!
这两个还是有很大区别的!
linux会根据线程的优先级、线程的闲置情况、I/O操作、紧要程度等对线程进行切换,进程CPU时间片的分配。更详细的可以参考《Linux内核设计与实现》
refer to:
1、https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431928972981094a382e5584413fa040b46d46cce48e000
2、https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p04_locking_critical_sections.html#id3
3、http://blog.51cto.com/suhaozhi/1924938
threading.local()、多线程里全局变量锁的更多相关文章
- threading.local和高级
threading.local特点 ①为每个线程开辟空间,让你进行存取值(根据线程ID来固定某个值) ②flask中没有threading.local,但是flask中的上下文管理的思想是借鉴的thr ...
- 网络编程 多线程/socketserver模块/ threading.local
线程:进程中负责程序执行的执行单元. 多线程:在1个进程中存在多个线程. 进程只是用来把资源集中在一起,而线程才是cpu上的执行单位. 每个进程都会默认有一个控制线程也叫作主线程. 进程之间是竞争关系 ...
- 多线程多进程学习threading,queue线程安全队列,线程间数据状态读取。threading.local() threading.RLock()
http://www.cnblogs.com/alex3714/articles/5230609.html python的多线程是通过上下文切换实现的,只能利用一核CPU,不适合CPU密集操作型任务, ...
- [Python 多线程] threading.local类 (六)
在使用threading.local()之前,先了解一下局部变量和全局变量. 局部变量: import threading import time def worker(): x = 0 for i ...
- 线程锁、threading.local(flask源码中用的到)、线程池、生产者消费者模型
一.线程锁 线程安全,多线程操作时,内部会让所有线程排队处理.如:list/dict/Queue 线程不安全 + 人(锁) => 排队处理 1.RLock/Lock:一次放一个 a.创建10个线 ...
- 锁丶threading.local丶线程池丶生产者消费者模型
一丶锁 线程安全: 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取. import threading v = ...
- 锁、threading.local、线程池
一.锁 Lock(1次放1个) 什么时候用到锁: 线程安全,多线程操作时,内部会让所有线程排队处理.如:list.dict.queue 线程不安全, import threading import t ...
- python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型
线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...
- 锁,threading local,以及生产者和消费者模型
1.锁:Lock(一次放行一个) 线程安全,多线程操作时,内部会让所有的线程排队处理. 线程不安全:+人=>排队处理 以后锁代码块 v=[] lock=threading.Lock()#声明锁 ...
随机推荐
- spring boot 使用不同的profile来加载不同的配置文件
在开发过程之中,经常需要在开发和测试环境中进行互相切换,当切换的同时需要加载相应的配置文件,因此要经常 性的对配置文件进行相应的修改,长此以往感到十分痛苦.如果能针对开发和测试环境分别建两个不同的配置 ...
- controller中,Failed to Initialize. Reason: TimeOut虚拟用花初始化超时
1.调整 Run-timesetting->Internet Protocol->references->Advaanced->Options 将HTTP-request co ...
- [Codeforces166B]Polygons 凸包
大致题意: 给你一个凸多边形A,和一个任意多边形B,判断B是否在A的内部 先对A的点集建凸包,然后枚举B中的点,二分判断是否在A的内部. 二分时可用叉积判断,详细见代码 #include<cst ...
- python解析Nginx访问日志
环境说明 python3+ pip install geoip2==2.9.0 nginx日志配置成json格式,配置如下: log_format json_log '{ "time&quo ...
- 【SpringBoot】关闭HttpClient无用日志
环境: SpringBoot pom依赖了apache.commons.HttpClient: <!--httpclient--> <dependency> <group ...
- Selenium之PhantomJS相关设置
设置PhantomJS请求头 默认情况下: from selenium import webdriver import time driver = webdriver.PhantomJS() driv ...
- NOIP 初赛笔记
// zj蒟蒻瑟瑟发抖.. // 停课了.要好好努力!——10月8日8:29于机房 1. 1946 年 美国 -> 第一台计算机 2. 真空电子管 -> 晶体管 -> 集成 -> ...
- luoguP3255 [JLOI2013]地形生成 动态规划
出题人语文真好... 各不相同的标号和高度 = 各不相同的标号 + 单独的高度... 第一问比较简单,考虑从大到小插入,在相同情况下,按关键值从小到大插入 这样子,关键大的元素一定会影响到关键小的元素 ...
- AtCoder Regular Contest 81
链接 C.Make a Rectangle 给出一堆木棍的长度 从中选4根,询问在能围成矩形的情况下,矩形的最大面积 开个map统计一下就行 分正方形和矩形分别统计即可 复杂度$O(n \log n) ...
- bzoj 2754 ac自动机
第一道AC自动机题目. 记一下对AC自动机的理解吧: AC自动机=Trie+KMP.即在Trie上应用KMP思想,实现多Pattern的匹配问题. 复杂度是预处理O(segma len(P)),匹配是 ...