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()#声明锁 ...
随机推荐
- day4迭代器&生成器&正则表达式
一.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不能后退,不过这也没什么,因为人们很少在迭代途中后退.另外,迭代器的一大优点 ...
- <node.js爬虫>制作教程
前言:最近想学习node.js,突然在网上看到基于node的爬虫制作教程,所以简单学习了一下,把这篇文章分享给同样初学node.js的朋友. 目标:爬取 http://tweixin.yueyishu ...
- dump调试函数
//dump调试函数if (!function_exists('dump')) { /* * dump调试函数 */ function dump($var) { $traces = debug_bac ...
- axios请求数据
1.安装axios模块 import axios from 'axios'; //安装方法 npm install axios //或 bower install axios 2.引入模块 直接引用: ...
- JAVA编程思想读书笔记(三)--RTTI
接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...
- arm Linux 驱动LED子系统 测试
Linux内核在3.0以上引入了设备树概念(具体哪个版本不清楚)在编译内核后需要将与之对应的dtb文件也下载人板子上才能使内核与硬件关联起来. dtb文件是有dts文件编译后生成的:例如 /* * C ...
- luogu P3383 【模板】线性筛素数
题目描述 如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示查询的范围和查询的个数. 接下来M行每行 ...
- [转]android中OnTouch和OnClick、 imagebutton的src和background有什么区别
1.OnTouch和OnClick有何区别? 专业回答 1.onClick就传入一个View对象,而 onTouch要传入一个View 对象和 MotionEvent的对象2.onTouch对控件 ...
- 更新teaching中fdSubjectID为null的老数据
UPDATE wkwke.tbTeachingV3 teaching SET teaching.fdSubjectID = ( SELECT fdValue FR ...
- linux基础命令学习 (十)Vi
1.vi的基本概念 基本上vi可以分为三种状态,分别是命令模式(command mode).插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下: ...