文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【Python之旅】第六篇(四):Pyt

2023-01-31 06:03

关注


    在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。


1.给线程加锁的原因

    我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。

    试想一下,在某一进程中,内存空间中存有一个变量对象的值为num=8,假如某一时刻有多个线程需要同时使用这个对象,出于这些线程要实现不同功能的需要,线程A需要将num减1后再使用,线程B需要将num加1后再使用,而线程C则是需要使用num原来的值8。由于这三个线程都是共享存储num值的内存空间的,并且这三个线程是可以同时并发执行的,当三个线程同时对num操作时,因为num只有一个,所以肯定会存在不同的操作顺序,想象一下下面这样操作过程:

第一步:线程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)

    先看下面一个未给线程加锁的程序代码:

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

    程序执行结果如下:

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)

    看下面给线程加锁的代码:

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

    程序执行结果如下:

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)

    为了更好的理解线程加锁的一个过程,把上面的代码修改为如下:

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

    执行结果如下:

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秒后再输出下一个数字,如此类推。

    为了更好的说明,我们可以看一下执行完此程序所花的时间:

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了。



    由上面的分析,我们不仅可以知道为什么要给线程加锁以及如何加锁,还可以比较清楚的知道线程加锁的一个过程了,以后在编写程序的时候,类似情况的,我们就应该要为线程加锁。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯