Python多线程锁
【Python之旅】第六篇(四):Python多线程锁
摘要: 在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。 1.给线程加锁的原因 我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一...
在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。
1.给线程加锁的原因
我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。
试想一下,在某一进程中,内存空间中存有一个变量对象的值为num=8,假如某一时刻有多个线程需要同时使用这个对象,出于这些线程要实现不同功能的需要,线程A需要将num减1后再使用,线程B需要将num加1后再使用,而线程C则是需要使用num原来的值8。由于这三个线程都是共享存储num值的内存空间的,并且这三个线程是可以同时并发执行的,当三个线程同时对num操作时,因为num只有一个,所以肯定会存在不同的操作顺序,想象一下下面这样操作过程:
1
2
3
4
5
|
第一步:线程A修改了num的值为 7 第二步:线程C不知道num的值已经发生了改变,直接调用了num的值 7 第三步:线程B对num值加 1 ,此时num值变为 8 第四步:线程B使用了num值 8 第五步:线程A使用了num值 8 |
因为num只有一个,而三个操作都针对一个num进行,所以上面的操作过程是完全有可能的,而原来线程A、B、C想要使用的num值应该分别为:7、9、8,这里却变成了:8、8、7。试想一下,如果这三个线程的操作对整个程序的执行是至关重要的,会造成什么样的后果?
因此出于程序稳定运行的考虑,对于线程需要调用内存中的共享数据时,我们就需要为线程加锁。
2.Python多线程锁
(1)
先看下面一个未给线程加锁的程序代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import threading import time number = 0 def run(num): global number number += 1 print number time.sleep( 1 ) for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
程序执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
上面是多个线程同时抢占同一内存空间的例子,但从执行结果中可以看到,程序依然顺序地输出1-19,而没有出现上面说的情况,那是仅仅是因为量少的原因,虽然执行正常,没有出错,但是并不代表不会出错。
(2)
看下面给线程加锁的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import threading import time number = 0 lock = threading.RLock() #调用threading模块中的RLock() def run(num): lock.acquire() #开始给线程加锁 global number number += 1 lock.release() #给线程解锁 print number time.sleep( 1 ) for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
程序执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
程序的执行结果肯定是会正常的,而在没有给线程加锁之前,则有可能是正常,注意这是两种完全不同的概念。
分析一下上面的程序:在某一线程修改num的值时,即给该线程加锁,该线程加锁后,只要是该线程需要调用的代码以及涉及的内存空间,都会立即被锁上,比如这里的"number+=1",其它线程虽然也在并发同时执行,但是不能执行"number+=1"这行代码的,即不能够去访问或修改num这一个共享内存空间的数据,只能等待该线程解锁后才能执行;当该线程解锁后,另一个线程马上加锁再来修改number的值,同时也不允许其它线程占用,如此类推,直到所有线程执行完毕。
根据上面的分析,为线程加锁就可以解决前面讲的线程安全问题。
(3)
为了更好的理解线程加锁的一个过程,把上面的代码修改为如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import threading import time number = 0 lock = threading.RLock() def run(num): lock.acquire() global number number += 1 print number time.sleep( 1 ) #把time.sleep( 1 )也锁在线程中 lock.release() for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
程序的执行结果跟上面是完全一样,但是程序的执行过程却大不一样,这里说一下修改代码后程序的执行过程:每输出一个数字,sleep 1秒后再输出下一个数字,如此类推。
为了更好的说明,我们可以看一下执行完此程序所花的时间:
1
2
3
4
5
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ time python thread_clock6.py | grep 'real' real 0m20.073s user 0m0.024s sys 0m0.008s |
由执行时间可以更好的说明上面的执行过程,但为什么会这样呢?下面来分析一下:由(2)的分析可知,虽然20个线程都是在同时并发执行run这一个函数,这里与(2)不同在于,(2)只加锁了涉及修改number的程序代码,而这里是加锁了整一个函数!所以在20个线程同时开始并发执行这个函数时,由于每一个线程的执行都要加锁,并且加锁的是整一个执行的函数,因此其它线程就无法调用该函数中的程序代码,只能等待一个线程执行完毕后再调用该函数的程序代码,如此一来,一个线程的执行需要sleep(1)一次,则20个线程的执行就需要sleep(1)20次,并且该过程是串行的,因此我们才看到如上面所说的程序执行过程,也可以清晰的知道为什么程序的执行需要20s了。
由上面的分析,我们不仅可以知道为什么要给线程加锁以及如何加锁,还可以比较清楚的知道线程加锁的一个过程了,以后在编写程序的时候,类似情况的,我们就应该要为线程加锁。
摘要: 在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。 1.给线程加锁的原因 我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一...
在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。
1.给线程加锁的原因
我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。
试想一下,在某一进程中,内存空间中存有一个变量对象的值为num=8,假如某一时刻有多个线程需要同时使用这个对象,出于这些线程要实现不同功能的需要,线程A需要将num减1后再使用,线程B需要将num加1后再使用,而线程C则是需要使用num原来的值8。由于这三个线程都是共享存储num值的内存空间的,并且这三个线程是可以同时并发执行的,当三个线程同时对num操作时,因为num只有一个,所以肯定会存在不同的操作顺序,想象一下下面这样操作过程:
1
2
3
4
5
|
第一步:线程A修改了num的值为 7 第二步:线程C不知道num的值已经发生了改变,直接调用了num的值 7 第三步:线程B对num值加 1 ,此时num值变为 8 第四步:线程B使用了num值 8 第五步:线程A使用了num值 8 |
因为num只有一个,而三个操作都针对一个num进行,所以上面的操作过程是完全有可能的,而原来线程A、B、C想要使用的num值应该分别为:7、9、8,这里却变成了:8、8、7。试想一下,如果这三个线程的操作对整个程序的执行是至关重要的,会造成什么样的后果?
因此出于程序稳定运行的考虑,对于线程需要调用内存中的共享数据时,我们就需要为线程加锁。
2.Python多线程锁
(1)
先看下面一个未给线程加锁的程序代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import threading import time number = 0 def run(num): global number number += 1 print number time.sleep( 1 ) for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
程序执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
上面是多个线程同时抢占同一内存空间的例子,但从执行结果中可以看到,程序依然顺序地输出1-19,而没有出现上面说的情况,那是仅仅是因为量少的原因,虽然执行正常,没有出错,但是并不代表不会出错。
(2)
看下面给线程加锁的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import threading import time number = 0 lock = threading.RLock() #调用threading模块中的RLock() def run(num): lock.acquire() #开始给线程加锁 global number number += 1 lock.release() #给线程解锁 print number time.sleep( 1 ) for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
程序执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
程序的执行结果肯定是会正常的,而在没有给线程加锁之前,则有可能是正常,注意这是两种完全不同的概念。
分析一下上面的程序:在某一线程修改num的值时,即给该线程加锁,该线程加锁后,只要是该线程需要调用的代码以及涉及的内存空间,都会立即被锁上,比如这里的"number+=1",其它线程虽然也在并发同时执行,但是不能执行"number+=1"这行代码的,即不能够去访问或修改num这一个共享内存空间的数据,只能等待该线程解锁后才能执行;当该线程解锁后,另一个线程马上加锁再来修改number的值,同时也不允许其它线程占用,如此类推,直到所有线程执行完毕。
根据上面的分析,为线程加锁就可以解决前面讲的线程安全问题。
(3)
为了更好的理解线程加锁的一个过程,把上面的代码修改为如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import threading import time number = 0 lock = threading.RLock() def run(num): lock.acquire() global number number += 1 print number time.sleep( 1 ) #把time.sleep( 1 )也锁在线程中 lock.release() for i in range( 20 ): t = threading.Thread(target=run, args=(i,)) t.start() |
执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
程序的执行结果跟上面是完全一样,但是程序的执行过程却大不一样,这里说一下修改代码后程序的执行过程:每输出一个数字,sleep 1秒后再输出下一个数字,如此类推。
为了更好的说明,我们可以看一下执行完此程序所花的时间:
1
2
3
4
5
|
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ time python thread_clock6.py | grep 'real' real 0m20.073s user 0m0.024s sys 0m0.008s |
由执行时间可以更好的说明上面的执行过程,但为什么会这样呢?下面来分析一下:由(2)的分析可知,虽然20个线程都是在同时并发执行run这一个函数,这里与(2)不同在于,(2)只加锁了涉及修改number的程序代码,而这里是加锁了整一个函数!所以在20个线程同时开始并发执行这个函数时,由于每一个线程的执行都要加锁,并且加锁的是整一个执行的函数,因此其它线程就无法调用该函数中的程序代码,只能等待一个线程执行完毕后再调用该函数的程序代码,如此一来,一个线程的执行需要sleep(1)一次,则20个线程的执行就需要sleep(1)20次,并且该过程是串行的,因此我们才看到如上面所说的程序执行过程,也可以清晰的知道为什么程序的执行需要20s了。
由上面的分析,我们不仅可以知道为什么要给线程加锁以及如何加锁,还可以比较清楚的知道线程加锁的一个过程了,以后在编写程序的时候,类似情况的,我们就应该要为线程加锁。
Python多线程锁的更多相关文章
- python 多线程锁机制
GIL(全局解释器锁) GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL 在Cpython解释器 ...
- python多线程锁lock/Rlock/BoundedSemaphore/Condition/Event
import time import threading lock = threading.RLock() n = 10 def task(arg): # 加锁,此区域的代码同一时刻只能有一个线程执行 ...
- 浅析Python多线程
学习Python多线程的资料很多,吐槽Python多线程的博客也不少.本文主要介绍Python多线程实际应用,且假设读者已经了解多线程的基本概念.如果读者对进程线程概念不甚了解,可参见知名博主 阮一峰 ...
- Python多线程和Python的锁
Python多线程 Python中实现多线程有两种方式,一种基于_thread模块(在Python2.x版本中为thread模块,没有下划线)的start_new_thread()函数,另一种基于th ...
- python多线程threading.Lock锁用法实例
本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 mutex = threading.Lock() #锁 ...
- python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio
摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...
- python多线程、线程锁
1.python多线程 多线程可以把空闲时间利用起来 比如有两个进程函数 func1.func2,func1函数里使用sleep休眠一定时间,如果使用单线程调用这两个函数,那么会顺序执行这两个函数 也 ...
- 第十五章、Python多线程同步锁,死锁和递归锁
目录 第十五章.Python多线程同步锁,死锁和递归锁 1. 引子: 2.同步锁 3.死锁 引子: 4.递归锁RLock 原理: 不多说,放代码 总结: 5. 大总结 第十五章.Python多线程同步 ...
- python多线程,event,互斥锁,死锁,递归锁,信号量
Python多线程/event 多线程-threading python的thread模块是⽐较底层的模块, python的threading模块是对thread做了⼀些包装的, 可以更加⽅便的被使⽤ ...
随机推荐
- Tornado 的教材
Tornado 的教材 作者:杨昆链接:https://www.zhihu.com/question/19707966/answer/12731684来源:知乎著作权归作者所有,转载请联系作者获得授权 ...
- 关于Weblogic Server(介绍)
Weblogic, 美国Oracle公司名下产品,是一个基于 J2EE 架构.可扩展的应用服务器. 本文档选取部分官方文档翻译 总览 支持多种类型的分布式应用 基于 SOA 应用的理想架构 完整实现 ...
- [BZOJ 1026] [SCOI 2009] Windy数 【数位DP】
题目链接:BZOJ - 1026 题目分析 这道题是一道数位DP的基础题,对于完全不会数位DP的我来说也是难题.. 对于询问 [a,b] 的区间的答案,我们对询问进行差分,求 [0,b] - [0,a ...
- cf D. On Sum of Fractions
http://codeforces.com/problemset/problem/397/D 题意:v(n) 表示小于等于n的最大素数,u(n)表示比n的大的第一个素数,然后求出: 思路:把分数拆分成 ...
- width:auto; 和 width:100%;的不同
width:auto:会将元素撑开至整个父元素width,但是会减去子节点自己的margin,padding或者border的大小.width:100%:会强制将元素变成和父元素一样的宽,并且添加额外 ...
- 贪心:SPOJ Backup Files
BACKUP - Backup Files no tags You run an IT company that backs up computer data for large offices. ...
- C语言数据类型的理解
数据类型的定义: 作为一种语言,必然有所谓的语言组成要素,就像日常生活中人们之间的交流一样,首先会有字,字再成词组,再来就是句子,后来呢就是段落等等.当然不同的字,词,句这些在一起,就会有不同的表达效 ...
- Java---实现运行任意目录下class中加了@MyTest的空参方法(实现图形界面)
说明: 因为上个代码,总是要输入完整的绝对路径,比较麻烦,于是,就写了这个小程序,直接进入文件对话框选择需要运行的class文件. 只需要提前输入完整的类名. 注意:加的MyTest必须打个包,加上: ...
- 搜索——[HAOI2012]添加号
题目:[HAOI2012]添加号 描述: [题目描述] 有一个由数字1,2,…9组成的数字串(长度不超过8),问如何将M(M<=5)个加号"+"插入到这个数字串中,使所形成的 ...
- POJ 2029 Get Many Persimmon Trees(水题)
题意:在w*h(最大100*100)的棋盘上,有的格子中放有一棵树,有的没有.问s*t的小矩形,最多能含有多少棵树. 解法:最直接的想法,设d[x1][y1][x2][y2]表示选择以(x1, y1) ...