Python零基础从小白打怪升级中~~~~~~~多线程

线程安全和锁

一、全局解释器锁

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

GIL全称global interpreter lock,全局解释器锁。

每个线程在执行的时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU。在CPython中,每一个Python线程执行前都需要去获得GIL锁 ,获得该锁的线程才可以执行,没有获得的只能等待 ,当具有GIL锁的线程运行完成后,其他等待的线程就会去争夺GIL锁,这就造成了,在Python中使用多线程,但同一时刻下依旧只有一个线程在运行 ,所以Python多线程其实并不是「并行」的,而是「并发」 。

看到下图,图中是Python中GIL的工作实例,其中有3个线程,线程与线程之间是顺序执行的 ,每个线程开始执行时都会去获得GIL,防止其他线程线程运行 ,每执行完一段时间后,就会释放GIL,让别的线程可以去争夺执行权限,如果自己本身也没有执行完,则本身也会参与这次争夺 。

image.png

# 多线程的代码
import threading, timedef add(n):sum = 0while sum < n:sum += 1print(f'sum:{sum}')if __name__ == '__main__':start = time.time()n = 500000000t1 = threading.Thread(target=add, args=[n // 2])t2 = threading.Thread(target=add, args=[n // 2])t1.start()t2.start()t1.join()t2.join()print('run time: %s' % str(time.time() - start))

image.png

# 单线程的代码
import timedef add(n):sum = 0while sum < n:sum += 1print(f'sum:{sum}')if __name__ == '__main__':start = time.time()add(500000000)print('run time: %s' % str(time.time() - start))

image.png

总结

  • GIL解决方法:

  • 使用其他语言写的python解释器(不推荐,还是用官方CPython好)
    eg:Jython(java);IronPython(.net);pypy(Python)

  • 不使用多线程,使用多进程-进程里加协程实现多任务来充分利用多核CPU (推荐)

  • 即使存在GIL 在有IO等待操作的程序中,还是多线程快,当然没有资源等待的还是单线程快(科学计算,累加等等)

但需要注意的是线程有了GIL后并不意味着使用Python多线程时不需要考虑线程安全 ,「GIL的存在是为了方便使用C语言编写CPython解释器的编写者,而顶层使用Python时依旧要考虑线程安全」 。

二、线程安全

当多个线程同时访问一个对象时,不管如何计算,如果调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。 如果出现了“脏数据”。则线程不安全。

脏数据 :产生脏数据的原因是,当一个线程在对数据进行修改时,修改到一半时另一个线程读取了未经修改的数据并进行修改。如何避免脏数据的产生呢?一个办法就是用join方法,即先让一个线程执行完毕再执行另一个线程。但这样的本质是把多线程变成了单线程,失去了多线程的意义。另一个办法就是用线程锁。

import threadingg_number = 0def hello():global g_numberfor i in range(1000000):  # 加的次数越大越容易出现资源竞争问题g_number += 1print(f'thd1运行的结果为:{g_number}')def world():global g_numberfor i in range(1000000):g_number += 1print(f'thd2运行的结果为:{g_number}')if __name__ == '__main__':thd1 = threading.Thread(target=hello)thd2 = threading.Thread(target=world)thd1.start()thd2.start()# 阻塞等待thd1.join()thd2.join()print(g_number)  # 结果随机 可能小于等于2000000

三、锁

锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。

一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。

threading模块中提供了5种最常见的锁,下面是按照功能进行划分:

  • 同步锁:lock(一次只能放行一个)
  • 递归锁:rlock(一次只能放行一个)
  • 条件锁:condition(一次可以放行任意个)
  • 事件锁:event(一次全部放行)
  • 信号量锁:semaphore(一次可以放行特定个)

1、同步锁

同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。

死锁: 指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象。

import threadingg_number = 0
lock = threading.Lock()def hello():global g_numberfor i in range(1000000):  # 加的次数越大越容易出现资源竞争问题with lock:g_number += 1print(f'thd1运行的结果为:{g_number}')def world():global g_numberfor i in range(1000000):with lock:g_number += 1print(f'thd2运行的结果为:{g_number}')if __name__ == '__main__':thd1 = threading.Thread(target=hello)thd2 = threading.Thread(target=world)thd1.start()thd2.start()# 阻塞等待thd1.join()thd2.join()print(g_number)  # 结果随机 可能小于等于2000000

2、递归同步锁

在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。

递归锁RLock:它内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

3、条件锁

条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()与notify()来控制线程执行的个数。

注意:条件锁可以自由设定一次放行几个线程。

import threadingcurrentRunThreadNumber = 0
maxSubThreadNumber = 10def task():global currentRunThreadNumberthread_name = threading.currentThread().namewith condLock:print("线程开始启动,并马上进入等待状态 : %s" % thread_name)condLock.wait()  # 暂停线程运行、等待唤醒print("线程唤醒了,开始运行后面的代码 : %s" % thread_name)currentRunThreadNumber += 1if __name__ == "__main__":condLock = threading.Condition()for i in range(maxSubThreadNumber):subThreadIns = threading.Thread(target=task)subThreadIns.start()while currentRunThreadNumber < maxSubThreadNumber:notifyNumber = int(input("请输入要唤醒几个线程:"))with condLock:condLock.notify(notifyNumber)  # 放行print("main thread run end")

4、事件锁

事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。

我们可以将事件锁看为红绿灯,当红灯时所有子线程都暂停运行,并进入“等待”状态,当绿灯时所有子线程都恢复“运行”。

image.png

import threadingmaxSubThreadNumber = 3def task():thread_name = threading.currentThread().nameprint("线程开始启动,并马上进入等待状态 : %s" % thread_name)eventLock.wait()  # 暂停运行,等待绿灯print("第一次绿灯打开,线程往下走:%s" % thread_name)eventLock.wait()  # 暂停运行,等待绿灯print("第二次绿灯打开,线程往下走:%s" % thread_name)if __name__ == "__main__":eventLock = threading.Event()for i in range(maxSubThreadNumber):subThreadIns = threading.Thread(target=task)subThreadIns.start()eventLock.set()  # 设置为绿灯eventLock.clear()  # 设置为红灯eventLock.set()

5、信号量锁

Semaphore()

信号量锁也是根据条件锁来做的,它与条件锁和事件锁的区别如下:

  • 条件锁:一次可以放行任意个处于“等待”状态的线程
  • 事件锁:一次可以放行全部的处于“等待”状态的线程
  • 信号量锁:通过规定,成批的放行特定(指定)个处于“上锁”状态的线程
import threading
import timemaxSubThreadNumber = 6def task():thread_name = threading.currentThread().namewith semaLock:print("线程获得锁,开始运行: %s" % thread_name)time.sleep(3)if __name__ == "__main__":semaLock = threading.Semaphore(2)for i in range(maxSubThreadNumber):subThreadIns = threading.Thread(target=task)subThreadIns.start()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/313397.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA plugins 好用的插件集

IDEA plugins RestfulToolkit 1. 安装插件 File–>Settings --> plugins --> RestfulToolkit 2.插件有点&#xff1a; 2.1、帮助把项目中的 RestURL 按照项目汇总出来&#xff0c;找到对应URL直接在IDEA上面进行请求测试。 2.2、开发Java Web页面项目&#xff0c;经…

学习笔记------时序约束之时钟周期约束

本文摘自《VIVADO从此开始》高亚军 主时钟周期约束 主时钟&#xff0c;即从FPGA的全局时钟引脚进入的时钟或者由高速收发器输出的时钟。 对于时钟约束&#xff0c;有三个要素描述&#xff1a;时钟源&#xff0c;占空比和时钟周期。 单端时钟输入 这里我们新建一个工程&#x…

【Proteus】51单片机对直流电机的控制

直流电机&#xff1a;输出或输入为直流电能的旋转电机。能实现直流电能和机械能互相转换的电机。把它作电动机运行时是直流电动机&#xff0c;电能转换为机械能&#xff1b;作发电机运行时是直流发电机&#xff0c;机 械能转换为电能。 直流电机的控制&#xff1a; 1、方向控制…

手撕AVL树(map和set底层结构)(1)

troop主页 今日鸡汤&#xff1a;Action may out always bring happiness;but there is no happiness without action. 行动不一定能带来快乐&#xff0c;但不行动一定不行 C之路还很长 手撕AVL树 一 AVL树概念二 模拟实现AVL树2.1 AVL节点的定义 三 插入更新平衡因子&#xff0…

vim相关指令

vim的各种模式及其转换关系图 vim 默认处于命令模式&#xff01;&#xff01;&#xff01; 模式之间转换的指令 除【命令模式】之外&#xff0c;其它模式要切换到【命令模式】&#xff0c;只需要无脑 ESC 即可&#xff01;&#xff01;&#xff01; [ 命令模式 ] 切换至 [ 插…

联合体共用体--第二十三天

1.结构体元素有各自单独的空间 共用体元素共享空间&#xff0c;空间大小由最大类型确定 2.结构体元素互不影响&#xff0c;共用体赋值会导致覆盖

javaWeb智能医疗管理系统

简介 在当今快节奏的生活中&#xff0c;智能医疗系统的崛起为医疗行业带来了一场革命性的变革。基于JavaWeb技术开发的智能医疗管理系统&#xff0c;不仅为医疗机构提供了高效、精准的管理工具&#xff0c;也为患者提供了更便捷、更个性化的医疗服务。本文将介绍一个基于SSM&a…

一些重新开始面试之后的八股文汇总

一、内存中各项名词说明 1、机器内存概念说明 linux中的free命令可以查看机器的内存使用情况&#xff0c;vmstat命令也可以 其中不容易被理解的是&#xff1a; 内存缓冲/存数&#xff08;buffer/cached&#xff09; 1.buffers和cache也是RAM划分出来的一部分地址空间 2.buff…

SQL优化——统计信息

文章目录 1、统计信息1.1、表的统计信息1.2、列的统计信息1.3、索引的统计信息 2、统计信息重要参数设置3、检查统计信息是否过期4、扩展统计信息5、动态采样6、定制统计信息收集策略 只有大表才会产生性能问题&#xff0c;那么怎么才能让优化器知道某个表多大呢&#xff1f;这…

移动硬盘故障解析:NTFS属性0字节,双击无法访问的数据恢复之道

一、故障现象&#xff1a;移动硬盘NTFS属性0字节&#xff0c;双击无法访问 在日常使用移动硬盘的过程中&#xff0c;有时我们会遇到一个令人困惑的问题&#xff1a;移动硬盘在接入电脑后&#xff0c;显示其属性为NTFS格式&#xff0c;但容量却显示为0字节&#xff0c;且双击无…

【Delphi 爬虫库 1】GET和POST方法

文章目录 1.最简单的Get方法实现2.可自定义请求头、自定义Cookie的Get方法实现3.提取响应协议头4.Post方法实现单词翻译 爬虫的基本原理是根据需求获取信息并返回。就像当我们感到饥饿时&#xff0c;可以选择自己烹饪食物、外出就餐&#xff0c;或者订外卖一样。在编程中&#…

airtest-ios真机搭建实践

首先阅读4 ios connection - Airtest Project Docs 在Windows环境下搭建Airtest对iOS真机进行自动化测试的过程相对复杂&#xff0c;因为iOS的自动化测试通常需要依赖Mac OS系统&#xff0c;但理论上借助一些工具和服务&#xff0c;Windows用户也可以间接完成部分工作。下面是…

软考中级工程师网络技术第二节网络体系结构

OSPF将路由器连接的物理网络划分为以下4种类型&#xff0c;以太网属于&#xff08;25&#xff09;&#xff0c;X.25分组交换网属于&#xff08;非广播多址网络NBMA&#xff09;。 A 点对点网络 B 广播多址网络 C 点到多点网络 D 非广播多址网络 试题答案 正确答案&#xff1a; …

数据结构初阶:二叉树(二)

二叉树链式结构的实现 前置说明 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在对二叉树结构掌握还不够深入&#xff0c;为了降低学习成本&#xff0c;此处手动快速创建一棵简单的二叉树&#xff0c;快速进入二…

学习Rust的第5天:控制流

Control flow, as the name suggests controls the flow of the program, based on a condition. 控制流&#xff0c;顾名思义&#xff0c;根据条件控制程序的流。 If expression If表达式 An if expression is used when you want to execute a block of code if a condition …

list基础知识

list 1.list 的定义和结构 list 是双向链表&#xff0c;是C的容器模板&#xff0c;其接收两个参数&#xff0c;即 list(a,b) 其中 a 表示指定容器中存储的数据类型&#xff0c;b 表示用于分配器内存的分配器类型&#xff0c;默认为 list <int>; list 的特点&#xff1a;…

springboot中mongodb连接池配置-源码分析

yml下spring.data.mongodb 以前mysql等在spring.xxx下配置&#xff0c;现在springboot新版本&#xff08;小编3.2.3&#xff09;在spring.data.xxx下了&#xff0c;如下所示&#xff0c;mongodb的配置在spring.data.mongodb下&#xff1a; 连接池相关参数配置-源码分析 拼接在…

STM32应用开发——BH1750光照传感器详解

STM32应用开发——BH1750光照传感器详解 目录 STM32应用开发——BH1750光照传感器详解前言1 硬件介绍1.1 BH1750简介1.2 硬件接线 2 软件编程2.1 软件原理2.1.1 IIC设备地址2.1.2 IIC读写2.1.3 BH1750指令集2.1.4 BH1750工作流程2.1.5 BH1750测量模式 2.2 测试代码2.3 运行测试…

学习了解大模型的四大缺陷

由中国人工智能学会主办的第十三届吴文俊人工智能科学技术奖颁奖典礼暨2023中国人工智能产业年会于2024年4月14日闭幕。 会上&#xff0c;中国工程院院士、同济大学校长郑庆华认为&#xff0c;大模型已经成为当前人工智能的巅峰&#xff0c;大模型之所以强&#xff0c;是依托了…

Java 设计模式系列:模板方法模式

简介 模板方法模式是一种行为型设计模式&#xff0c;它定义一个操作中的算法骨架&#xff0c;将一些步骤推迟到子类中。模板方法模式使得子类可以不改变一个算法的结构&#xff0c;即可重定义该算法的某些特定步骤。 在模板方法模式中&#xff0c;抽象类中定义了一系列基本操…