Python3 多线程编程 - 学习笔记
线程
什么是线程
官方定义:
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
说人话:
假如进程是保洁公司,线程就是公司的员工。当公司接到任务时,干活的是员工,而进程负责分配员工。可以好几个员工擦一块玻璃,也可以一个员工收拾一个屋子。
特点
独立调度和分派的基本单位
一个公司里至少得有一个员工,才能干活。公司分配各项工作给团队,最后团队还是会分配给个人。
轻型实体
每个线程占用的系统资源非常少。
可并发执行
多个线程可以同时工作,就像公司里的员工可以一切干活。
共享进程资源
所有的线程共享该进程所拥有的资源。
例如: 所有线程地址空间都相同(进程的地址空间),就好比一家公司的所有员工的工作地址都为公司的所在地。
线程与进程的关系
继续用保洁公司举例子
- 公司本身是进程,公司里又很多员工,每个员工都是一个线程,同时公司里还有很多共享的资源,比如:扫把、墩布、毛巾、饮水机等。
- 但是与现实模型不同的是,这些人是由多个 cpu 控制的,例如 4 个 cpu 对应 40 个人,cpu 需要切换控制。
- 真正执行工作的是公司员工,也就是进程的任务靠线程执行
- 这些人可以并行工作,处理事情。也就是多线程可以同时运行。
- 当大家访问公共资源的时候会有冲突,例如:都要出去擦玻璃,就剩一条毛巾了,这时就需要排队。在进程中就叫做上锁。
Python3中的多线程
全局解释器锁(GIL)
GIL是啥?
GIL并不是Python语言的特性,它是在现实Python解释器时引用的一个概念。
GIL只在CPython解释器上存在。作用是保证同一时间内只有一个线程在执行。
解决解释器中多个线程的竞争资源问题
GIL对Python程序有啥影响?
Python中同一时刻有且只有一个线程会执行。
因此,Python中的多线程并不算是真正意义上的多线程。
Python中的多个线程由于GIL锁的存在无法利用多核CPU。
因此,Python中的多线程不适合计算机密集型的程序。
计算密集型程序
计算密集型又叫CUP密集型,这类程序绝大部分运行时间都消耗在CPU计算上,这个时候,不论你开多少线程,他用的时间都是这么多,甚至比原来时间还长,因为GIL一个时刻只让你执行一个线程,大部分计算密集型任务你分了很多线程但是依然会按照代码顺序线性执行。分很多线程没有什么改善,反而因为代码的冗杂可能更加慢。
IO密集型程序
IO密集型顾名思义就是主要进行I/O操作,90%以上的时间都花费在网络、硬盘、输入输出上了,CPU执行完命令之后其实就没事干了,就可以释放内存来执行下一条命令了,不用让CPU在那干等着,这样就能大大提高程序的运行效率。
改善GIL产生的问题
- 使用更高版本的解释器,优化对Python的解释
- 变更解释器,如(JPython),但可能因为相对小众,支持的模块相对较少,开发效率变低
- 用多进程方案替代多线程
Python3关于多线程的模块
Python的标准库提供了两个模块:_thread和threading
_thread
在Python3之前为thread,有于存在缺陷,不建议使用,在Python3中封装成_thread
threading
thread的继承者,绝大多数情况下使用threading就够了。
多线程使用
直接调用
把一个函数传入创建Thread实例,然后调用start()开始执行
import threading
import time
def loop():
#threading.current_thread().name获取当前线程的名字
print(f"线程( {threading.current_thread().name} )正在执行……")
num = 0
while num < 5:
num += 1
print(f"线程( {threading.current_thread().name} )>>> {num}")
time.sleep(1)
print(f"线程( {threading.current_thread().name}) 执行结束") if __name__ == '__main__':
print(f"线程( {threading.current_thread().name} )正在执行……")
# 创建线程实体,target导入的函数,name线程的名字,初始为Tread1,之后类推2,3,4
# 如果导入函数有参数,使用 arg=() 添加函数参数
t = threading.Thread(target=loop,name='LoopThread')
# 启动线程
t.start()
# 等待多线程结束
t.join()
print(f"线程( {threading.current_thread().name} )执行结束")执行结果如下:
线程( MainThread )正在执行……
线程( LoopThread )正在执行……
线程( LoopThread )>>> 1
线程( LoopThread )>>> 2
线程( LoopThread )>>> 3
线程( LoopThread )>>> 4
线程( LoopThread )>>> 5
线程( LoopThread) 执行结束
线程( MainThread )执行结束
继承自threading.Thread调用
- 直接继承Thread
- 重写run函数,run函数代表的是真正执行的功能
- 类实例可以直接运行
import threading
import time
# 1. 类需要继承自threading.Thread
class MyThread(threading.Thread):
def __init__(self, arg):
super(MyThread, self).__init__()
self.arg = arg # 2 必须重写run函数,run函数代表的是真正执行的功能
def run(self):
time.sleep(2)
print(f"Run >>> {self.arg}") for i in range(1,4):
t = MyThread(i)
t.start()
t.join() print("End")执行结果如下:
Run >>> 1
Run >>> 2
Run >>> 3
End
守护线程
不设置守护线程
import time
import threading def fun():
print("启动Fun")
time.sleep(2)
print("结束Fun") print("启动Main")
t = threading.Thread(target=fun)
t.start()
time.sleep(1)
print("结束Main")
运行结果
启动Main
启动Fun
结束Main
结束Fun
设置守护线程
import time
import threading def fun():
print("启动Fun")
time.sleep(2)
print("结束Fun") print("启动Main")
t = threading.Thread(target=fun)
t.setDaemon(True)
t.start()
time.sleep(1)
print("结束Main")
运行结果
启动Main
启动Fun
结束Main
如果你设置一个线程为守护线程,,就表示你认为此线程不重要,在进程退出的时候,不用等待这个线程即可退出。
thread.setDaemon(True|False)表示此线程是否为守护线程。但此语句必须加在thread.start()之前
常用函数
threading.enumerate():返回一个包含正在运行的线程的list
threading.activeCount():返回正在运行的线程数量,效果跟 len(threading.enumerate)相同
thr.setName(): 给线程设置名字thr.getName(): 得到线程的名字thr表示线程实例,如
t1.getName()thr.getName()获取指定线程的名字,threading.current_thread().name获取当前线程的名字
共享变量
多线程同时访问同一变量时,会产生共享变量的问题,造成变量冲突产生问题。
实例
执行多线程,对同一变量进行加减操作,代码如下。
import threading sum = 0
loopSum = 1000000 def myAdd():
global sum, loopSum
for i in range(1, loopSum):
sum += 1 def myMinu():
global sum, loopSum
for i in range(1, loopSum):
sum -= 1 if __name__ == '__main__':
print(f"Starting ....{sum}") t1 = threading.Thread(target=myAdd, args=())
t2 = threading.Thread(target=myMinu, args=()) t1.start()
t2.start() t1.join()
t2.join() print(f"Done .... {sum}")两次分别如下执行结果如下:
Starting ....0
Done .... -278763
Starting ....0
Done .... 662850
可以发现,运算结果与我们传统认知不同。原因是Python在内部实现运算时是一个复杂的过程,同时对
sum进行修改时产生的相互干扰,故出现未知错误,导致结果出错。解决方法:上锁
锁(Lock):是一个标志,表示一个线程在占用一些共享资源.
那些资源需要上锁?
需要被共享使用的,可能产生使用冲突的
注意:
避免产生死锁,导致程序陷入死循环
线程安全问题:
- 如果一个资源,他对于多线程来讲,不用加锁也不会引起任何问题,则称为线程安全。
- 线程不安全变量类型: list, set, dict
- 线程安全变量类型: queue
使用案例,代码如下:
import threading sum = 0
loopSum = 1000000
lock = threading.Lock()
def myAdd():
global sum, loopSum
for i in range(1, loopSum):
# 上锁,申请锁
lock.acquire()
sum += 1
# 释放锁
lock.release() def myMinu():
global sum, loopSum for i in range(1, loopSum):
lock.acquire()
sum -= 1
lock.release() if __name__ == '__main__':
print(f"Starting ....{sum}") t1 = threading.Thread(target=myAdd, args=())
t2 = threading.Thread(target=myMinu, args=()) t1.start()
t2.start() t1.join()
t2.join() print(f"Done .... {sum}")
执行结果:
Starting ....0
Done .... 0
Python3 多线程编程 - 学习笔记的更多相关文章
- 多线程编程学习笔记——async和await(一)
接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...
- 多线程编程学习笔记——async和await(二)
接上文 多线程编程学习笔记——async和await(一) 三. 对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...
- 多线程编程学习笔记——async和await(三)
接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五. 处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...
- 多线程编程学习笔记——使用异步IO(一)
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
- 多线程编程学习笔记——异步调用WCF服务
接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...
- 多线程编程学习笔记——使用异步IO
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- Java多线程编程(学习笔记)
一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过 ...
- [Java123] JDBC and Multi-Threading 多线程编程学习笔记
项目实际需求:DB交互使用多线程实现 多线程编程基础:1.5 :( (假设总分10) 计划一个半月从头学习梳理Java多线程编程基础以及Oracle数据库交互相关的多线程实现 学习如何通过代码去验证 ...
随机推荐
- 泛型(Generic)接口
泛型接口例子:一个学生有一个独一无二的ID,但是每个学生的姓名不一定是唯一的. class Program { static void Main(string[] args) { Student< ...
- JAVA SE Download
{ //https://www.oracle.com/technetwork/java/javase/downloads/index.html }
- Android中的第一个NDK的例子
前几天研究了JNI技术后,想在Android上试一试研究结果,查阅了很多资料后,总结如下步骤: 首先来看一下什么是NDK NDK 提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动 ...
- 38 ubuntu/windows双系统安装
0 引言 (1)针对bios 和 uefi引导,安装方式略有不同. (2)针对nvidia显卡,在安装时需要特殊设置. 1 EasyBCD安装方式介绍-适用于bios引导方式 参考百度经验贴安装即可, ...
- ST表 (模板) 洛谷3865
题目背景 这是一道ST表经典题——静态区间最大值 请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O(1) O(1) 题目描述 给定一个长度为 N N 的数列,和 M M ...
- 手机网站Meta的使用
meta指元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词. 标签位于文档的头部,不包含任何内容. 标签的属性定义了与文档相关联的名称/值对. ...
- python中字典排序
一.Python的排序 1.reversed() 这个很好理解,reversed英文意思就是:adj. 颠倒的:相反的:(判决等)撤销的 print list(reversed(['dream','a ...
- sklearn中回归器性能评估方法
explained_variance_score() mean_absolute_error() mean_squared_error() r2_score() 以上四个函数的相同点: 这些函数都有一 ...
- Flutter 打包报错 : Unknown FLUTTER_BUILD_MODE: xxx
概要 在集成flutter 工程之后,我们的工程在debug 和release 模式下都没什么问题,一切都很顺利.但是我们在打企业包的时候却出现了错误: Showing Recent Errors O ...
- iOS开发系列-iOS适配
概述 发布iPhone X 系统版本为iOS11, 由于刘海屏原因需要对新的机型做适配. iPhone X safeArea iOS11苹果提出safeArea替代iOS7引入 topLayoutGu ...