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做了⼀些包装的, 可以更加⽅便的被使⽤ ...
随机推荐
- C# ERP开发框架
C/S系统开发框架-企业版 V4.0 (Enterprise Edition) 简介: http://www.csframework.com/cs-framework-4.0.htm 视频下载: 百度 ...
- Day12(补充) Python操作MySQL
本篇对于Python操作MySQL主要使用两种方式: 原生模块 pymsql ORM框架 SQLAchemy pymsql pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb ...
- ipython与python的区别
http://mba.shengwushibie.com/itbook/BookChapter.asp?id=8745 http://www.cnblogs.com/yangze/archive/20 ...
- 转:基于ASP.NET的Comet长连接技术解析
原文来自于: Comet技术原理 来自维基百科:Comet是一种用于web的技术,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询和iframe流. 简单的 ...
- Node.js教程
Node.js是什么? Node.js是建立在谷歌Chrome的JavaScript引擎(V8引擎)的服务器端平台.Node.js是由瑞恩·达尔在2009年开发的,它的最新版本是v0.10.36. N ...
- javascript design patterns
http://jsdesignpatterns.com/ http://www.joezimjs.com/tag/design-patterns/ http://codecube.net/#archi ...
- Csharp 简单操作Word模板文件
原文:Csharp 简单操作Word模板文件 1.创建一个模板的Word文档 Doc1.dot 内容为: To: <Name> Sub:<Subject> Website i ...
- node.js基础模块http、网页分析工具cherrio实现爬虫
node.js基础模块http.网页分析工具cherrio实现爬虫 一.前言 说是爬虫初探,其实并没有用到爬虫相关第三方类库,主要用了node.js基础模块http.网页分析工具cherri ...
- COJ 2124 Day8-例1
Day8-例1 难度级别:B: 运行时间限制:1000ms: 运行空间限制:256000KB: 代码长度限制:2000000B 试题描述 给定n.m的值,求
- COJ 0601&0602 动态规划(二)及加强
未加强传送门0601:http://oj.cnuschool.org.cn/oj/home/addSolution.htm?problemID=571 加强传送门0602:http://oj.cnus ...