在前一篇文章 python线程创建和传参 中我们介绍了关于python线程的一些简单函数使用和线程的参数传递,使用多线程可以同时执行多个任务,提高开发效率,但是在实际开发中往往我们会碰到线程同步问题,假如有这样一个场景:对全局变量累加1000000次,为了提高效率,我们可以使用多线程完成,示例代码如下:

# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:何以解忧
@Blog(个人博客地址): shuopython.com
@WeChat Official Account(微信公众号):猿说python
@Github:www.github.com @File:python_thread_lock.py
@Time:2019/10/17 21:22 @Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
# 导入线程threading模块
import threading # 声明全局变量
g_num = 0 def my_thread1(): # 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
g_num = g_num + 1 def my_thread2(): # 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
g_num = g_num + 1 def main(i): # 声明全局变量
global g_num
# 初始化全局变量,初始值为 0
g_num = 0
# 创建两个线程,对全局变量进行累计加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2) # 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 获取全局变量的值
print("第%d次计算结果:%d "% (i,g_num)) if __name__ == "__main__": # 循环4次,调用main函数,计算全局变量的值
for i in range(1,5):
main(i)

输出结果:

第1次计算结果:1262996
第2次计算结果:1661455
第3次计算结果:1300211
第4次计算结果:1563699

what ? 这是什么操作??看着代码好像也没问题,两个线程,各自累加1000000次,不应该输出是2000000次吗?而且调用了4次main函数,每次输出的结果还不同!!

一.线程共享全局变量

分析下上面的代码:两个线程共享全局变量并执行for循环1000000,每次自动加1,我们都知道两个线程都是同时在运行,也就是说两个线程同时在执行 g_num = g_num + 1 操作, 经过我们冷静分析一波,貌似结果还是应该等于2000000,对不对?

首先,我们将上面全局变量自动加 1 的代码分为两步:

第一步:g_num + 1
第二步:将 g_num + 1 的结果赋值给 g_num

由此可见,执行一个完整的自动加1过程需要两步,然而线程却是在同时运行,谁也不能保证线程1的第一步和第二步执行完成之后才执行线程2的第一步和第二步,执行的过程充满随机性,这就是导致每次计算结果不同的原因所在!

举个简单的例子:

假如当前 g_num 值是100,当线程1执行第一步时,cpu通过计算获得结果101,并准备把计算的结果101赋值给g_num,然后再传值的过程中,线程2突然开始执行了并且执行了第一步,此时g_num的值仍未100,101还在传递的过程中,还没成功赋值,线程2获得计算结果101,并准备传递给g_num,经过一来一去这么一折腾,分明做了两次加 1 操作,g_num结果却是101,误差就由此产生,往往循环次数越多,产生的误差就越大。

二.线程互斥锁

为了避免上述问题,我们可以利用线程互斥锁解决这个问题。那么互斥锁到底是个什么原理呢?互斥锁就好比排队上厕所,一个坑位只能蹲一个人,只有占用坑位的人完事了,另外一个人才能上!

1.创建互斥锁

导入线程模块,通过 threading.Lock() 创建互斥锁.

# 导入线程threading模块
import threading # 创建互斥锁
mutex = threading.Lock()

2.锁定资源/解锁资源

acquire() — 锁定资源,此时资源是锁定状态,其他线程无法修改锁定的资源,直到等待锁定的资源释放之后才能操作;

release() — 释放资源,也称为解锁操作,对锁定的资源解锁,解锁之后其他线程可以对资源正常操作;

以上面的代码为列子:想得到正确的结果,可以直接利用互斥锁在全局变量 加1 之前 锁定资源,然后在计算完成之后释放资源,这样就是一个完整的计算过程,至于应该是哪个线程先执行,无所谓,先到先得,凭本事说话….演示代码如下:

# !usr/bin/env python
# -*- coding:utf-8 _*-
"""
@Author:何以解忧
@Blog(个人博客地址): shuopython.com
@WeChat Official Account(微信公众号):猿说python
@Github:www.github.com @File:python_thread_lock.py
@Time:2019/10/18 21:22 @Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
"""
# 导入线程threading模块
import threading # 声明全局变量
g_num = 0
# 创建互斥锁
mutex = threading.Lock() def my_thread1(): # 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
# 锁定资源
mutex.acquire()
g_num = g_num + 1
# 解锁资源
mutex.release() def my_thread2(): # 声明全局变量
global g_num
# 循环 1000000 次,每次累计加 1
for i in range(0,1000000):
# 锁定资源
mutex.acquire()
g_num = g_num + 1
# 解锁资源
mutex.release() def main(i): # 声明全局变量
global g_num
# 初始化全局变量,初始值为 0
g_num = 0
# 创建两个线程,对全局变量进行累计加 1
t1 = threading.Thread(target=my_thread1)
t2 = threading.Thread(target=my_thread2) # 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 获取全局变量的值
print("第%d次计算结果:%d "% (i,g_num)) if __name__ == "__main__": # 循环4次,调用main函数,计算全局变量的值
for i in range(1,5):
main(i)

输出结果:

第1次计算结果:2000000
第2次计算结果:2000000
第3次计算结果:2000000
第4次计算结果:2000000

由此可见,全局变量计算加上互斥锁之后,不论执行多少次,计算结果都相同。注意:互斥锁一旦锁定之后要记得解锁,否则资源会一直处于锁定状态;

三.线程死锁

1.单个互斥锁的死锁:acquire()/release() 是成对出现的,互斥锁对资源锁定之后就一定要解锁,否则资源会一直处于锁定状态,其他线程无法修改;就好比上面的代码,任何一个线程没有释放资源release(),程序就会一直处于阻塞状态(在等待资源被释放),不信你可以试一试~

2.多个互斥锁的死锁:在同时操作多个互斥锁的时候一定要格外小心,因为一不小心就容易进入死循环,假如有这样一个场景:boss让程序员一实现功能一的开发,让程序员二实现功能二的开发,功能开发完成之后一起整合代码!

# 导入线程threading模块
import threading
# 导入线程time模块
import time # 创建互斥锁
mutex_one = threading.Lock()
mutex_two = threading.Lock() def programmer_thread1(): mutex_one.acquire()
print("我是程序员1,module1开发正式开始,谁也别动我的代码")
time.sleep(2) # 此时会堵塞,因为这个mutex_two已经被线程programmer_thread2抢先上锁了,等待解锁
mutex_two.acquire()
print("等待程序员2通知我合并代码")
mutex_two.release() mutex_one.release() def programmer_thread2():
mutex_two.acquire()
print("我是程序员2,module2开发正式开始,谁也别动我的代码")
time.sleep(2) # 此时会堵塞,因为这个mutex_one已经被线程programmer_thread1抢先上锁了,等待解锁
mutex_one.acquire()
print("等待程序员1通知我合并代码")
mutex_one.release() mutex_two.release() def main(): t1 = threading.Thread(target=programmer_thread1)
t2 = threading.Thread(target=programmer_thread2) # 启动线程
t1.start()
t2.start()
# 阻塞函数,等待线程结束
t1.join()
t2.join()
# 整合代码结束
print("整合代码结束 ") if __name__ == "__main__": main()

输出结果:

我是程序员1,module1开发正式开始,谁也别动我的代码
我是程序员2,module2开发正式开始,谁也别动我的代码

分析下上面代码:程序员1在等程序员2通知,程序员2在等程序员1通知,两个线程都陷入阻塞中,因为两个线程都在等待对方解锁,这就是死锁!所以在开发中对于死锁的问题还是需要多多注意!

四.重点总结

1.线程与线程之间共享全局变量需要设置互斥锁;

2.注意在互斥锁操作中 acquire()/release() 成对出现,避免造成死锁;

猜你喜欢:

1.python线程创建和传参

2.python函数-缺省参数

3.python局部变量和全局变量

转载请注明:猿说Python » Python线程互斥锁Lock

技术交流、商务合作请直接联系博主
扫码或搜索:猿说python
猿说python
微信公众号 扫一扫关注

python线程互斥锁Lock(29)的更多相关文章

  1. python 之 并发编程(守护线程与守护进程的区别、线程互斥锁、死锁现象与递归锁、信号量、GIL全局解释器锁)

    9.94 守护线程与守护进程的区别 1.对主进程来说,运行完毕指的是主进程代码运行完毕2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕​详细解释:1.主 ...

  2. Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁

    Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...

  3. Python 35 线程(2)线程特性、守护线程、线程互斥锁

    一:线程特性介绍 from threading import Thread import time n=100 def task(): global n n=0 if __name__ == '__m ...

  4. python并发编程-进程间通信-Queue队列使用-生产者消费者模型-线程理论-创建及对象属性方法-线程互斥锁-守护线程-02

    目录 进程补充 进程通信前言 Queue队列的基本使用 通过Queue队列实现进程间通信(IPC机制) 生产者消费者模型 以做包子买包子为例实现当包子卖完了停止消费行为 线程 什么是线程 为什么要有线 ...

  5. GIL与线程互斥锁

    GIL 是解释器级别的锁,是限制只有一个原生线程运行,防止多个原生线程之间修改底层的共享数据.而线程互斥锁是防止多个线程同时修改python内存空间的共享数据.

  6. 20190102(多线程,守护线程,线程互斥锁,信号量,JoinableQueue)

    多线程 多进程: 核心是多道技术,本质上就是切换加保存技术. 当进程IO操作较多,可以提高程序效率. 每个进程都默认有一条主线程. 多线程: 程序的执行线路,相当于一条流水线,其包含了程序的具体执行步 ...

  7. 8.12 day31 进程间通信 Queue队列使用 生产者消费者模型 线程理论 创建及对象属性方法 线程互斥锁 守护线程

    进程补充 进程通信 要想实现进程间通信,可以用管道或者队列 队列比管道更好用(队列自带管道和锁) 管道和队列的共同特点:数据只有一份,取完就没了 无法重复获取用一份数据 队列特点:先进先出 堆栈特点: ...

  8. 互斥锁lock、信号量semaphore、事件Event、

    1.互斥锁lock 应用在多进程中互斥所lock:互斥锁是进程间的get_ticket互相排斥进程之间,谁先枪占到资源,谁就先上锁,等到解锁之后,下一个进程在继续使用.# 语法: 上锁: lock.a ...

  9. C# 多线程编程之锁的使用【互斥锁(lock)和读写锁(ReadWriteLock)】

    多线程编程之锁的使用[互斥锁(lock)和读写锁(ReadWriteLock)] http://blog.csdn.net/sqqyq/article/details/18651335 多线程程序写日 ...

随机推荐

  1. Linux core dump 诊断进程奔溃退出

           最近项目中出现了一个问题,服务器端程序会突然崩溃退出,我们采取了coredump技术以找到崩溃原因,即确定进程退出时正在执行的函数是哪个,其状态如何.       如果系统开启了core ...

  2. qsing

    qsing1 1.低仿机器人 一道大模拟 2.放爆竹 小辉原本想让小明告诉他,如果同时点燃n串雷,最多会有多长的时间至少有两串雷爆炸的声音是一样的. 但是小辉觉得这个问题真是太简单了,所以决定问小明, ...

  3. angular2事件触发

    输入框输入过程触发Select()方法. <input type="text" name="code" [(ngModel)]="code&qu ...

  4. maven下载与安装

    1.下载地址:http://maven.apache.org/download.cgi(Windows平台下载*.zip压缩包,Linux平台下载*.gz压缩包) 2.解压到E:\JAVA\Maven ...

  5. 启动Django项目报错

    今天一时手欠将电脑名字改成了中文,在启动Django或Flask项目的时候一直报下面的错误 问题描述: 环境配置成功,Django成功pip,运行项目报错,浏览器输入127.0.0.1:8000报错 ...

  6. [WEB安全]IIS-PUT漏洞

    目录 0x00 IIS简介 0x01 Put漏洞造成原因 0x02 实验环境搭建 0x03 需要用到的工具 0x04 IIS-PUT漏洞演示实战 0x05 常见请求协议 0x06 漏洞修复建议 0x0 ...

  7. Windows使用Latex

    目录 安装Texlive 安装TeXstudio 编写简单的文章 教程 安装Texlive 到清华大学开源软件镜像站下载Texlive2019.iso文件 下载之后,如果有光驱就装载,没有的话就解压. ...

  8. Get Argument Values From Linq Expression

    Get Argument Values From Linq Expression If you even find yourself unpacking an expression in C#, yo ...

  9. 你了解 Virtual DOM 吗?解释一下它的工作原理

    Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本.它是一个节点树,它将元素.它们的属性和内容作为对象及其属性. React 的渲染函数从 Rea ...

  10. linux 之oracle静默安装

    一.安装前准备工作1.修改主机名#vi /etc/hosts   //并添加内网IP地址对应的hostname,如下127.0.0.1           localhost::1           ...