4.多线程编程技术
大约 5 分钟学习笔记测试开发测试工具开发
一、多线程的概述
1. 进程与线程
- 进程 :在系统中正在运行的一个 应用程序 ,程序一旦运行就是进程。
- 进程 --- 资源分配的最小单位。
- 线程 : 系统分配处理时间资源的基本单元,或者说进程之内独立执行的一个单元试行流。
- 线程 --- 程序执行的最小单位。
- 进程 中最少有一个线程。
2.进程的组成
内存 : 每个进程的内存是相互独立的。
文件/网络句柄 : 它们是所有的进程所共有的。
线程
提示
所有的 线程 共享 该进程的所有资源。
:::
3. 进程与线程---汇总
- 进程有分配已大部分的内存,而线程只需要分配一部分栈就可以了。
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
4.多线程
- 使用线程可以把 占据长时间 的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人。
- 程序的运行速度可能加快。
- 在一些 等待的任务 的实现上,线程就比较有用。
5.多线程的执行方式
- 串行 :依次执行所有的线程,上一个线程没有执行完之前,下一个线程不会执行。
- 并行 : 所有线程同时执行。
- 并发 : 所有线程依次 交替 执行。
二、Threading 模块
1. Threading 模块 常用方法
Thread()
: 创建线程,target
为需要执行的函数,args
为执行函数的实参(参数为元组)run()
: 用以表示线程活动的方法。start()
: 启动线程活动。join([time])
: 等待至线程中止。这阻塞调用线程直至线程的join()
方法被调用中止 - 正常退出或者抛出未处理的异常 - 或者是可选的超时发生。isAlive()
: 返回线程是否活动的。gitName()
: 返回线程名。setName()
: 设置线程名。threading.currentThread()
: 返回当前的线程变量。threading.enumerate()
: 返回一个包含正在运行的线程的list
。正在运行 指线程启动后、结束前,不包含 启动前和终止后的线程。threading.activeCount()
: 返回正在运行的线程数量,与len(threading.enumerate())
有相同的结果。
2.多线程案例
普通方法:
import time
def doing(something):
time.sleep(2)
print("正在做 >>> :", something)
start_time = time.time()
doing("在学习")
doing("在加班")
end_time = time.time()
print("使用时间:", end_time-start_time)
多线程方法:
import threading
import time
def doing(something):
print("正在做 >>> :", something)
time.sleep(2)
start_time = time.time()
# 1.创建线程
"""
target : 需要执行的函数名
args : 执行函数的实参(参数为元组)一个参数时需要加 逗号“,”
"""
a = threading.Thread(target=doing, args=("在学习",))
b = threading.Thread(target=doing, args=("在加班",))
# 2.启动子线程
a.start()
b.start()
# 3.阻塞主线程,直到子线程全部结束
a.join()
b.join()
end_time = time.time()
print("使用时间:", end_time-start_time)
三、 多线程技术
1. 全局解释器锁(GIL)
在 CPython 中,无论 CPU 有多少核,同时只能执行 一个线程,这是用于 GIL 的存在导致的。
可以把 GIL 看作是执行任务的 “通行证”,并且在一个 Python 进程中, GIL 只有一个。
示例:
某些情况下,多个线程同时操作对一个变量时,可能会导致数据混乱,所以需要 加锁 ,来防止变量依次更改 。
import threading
import time
money = 1000
lock = threading.Lock()
def test(num):
global money
lock.acquire() # 加锁
userMoney = money
userMoney += num
time.sleep(2)
money = userMoney
lock.release() # 解锁
if __name__ == '__main__':
# 创建子线程
t1 = threading.Thread(target=test, args=(500,))
t2 = threading.Thread(target=test, args=(-200,))
# 启动子线程
t1.start()
t2.start()
# 阻塞
t1.join()
t2.join()
print(f"余额为:{money}")
2.守护进程
在进程想要退出进程的时候,不需要等待自己运行结束,直接退出 即可,
t1.setDaemon(True)
示例:
不论子线程是否执行结束,主线程执行完后直接结束。
import threading
import time
def doing(something):
print("正在做 >>> :", something)
time.sleep(2)
print(something, "结束了")
if __name__ == '__main__':
start_time = time.time()
a = threading.Thread(target=doing, args=("在学习",))
b = threading.Thread(target=doing, args=("在加班",))
a.setDaemon(True) # 守护线程
b.setDaemon(True)
# 2.启动子线程
a.start()
b.start()
for one in range(2):
print("主线程在运行:", one)
end_time = time.time()
print("使用时间:", end_time-start_time)
提示
- 如果只有
t1.start()
,没有t1.join()
和t1.setDaemon(True)
- 所有线程全部运行完成,这用会出现 主线程结束后,子线程继续运行 , 直到结束。
- 如果只有
t1.start()
、t1.join()
,没有t1.setDaemon(True)
,- 主线程结束前会等待所有的子线程运行完成,在结束。
- 如果有
t1.start()
、t1.setDaemon(True)
,没有t1.join()
- 主线程完成, 所有子线程都 中断。
3. 死锁
在线程间 共享多个资源 的时候,如果两个线程分别占有一部分资源并且 同时等待 对方的资源,就会造成 死锁。
示例:
import threading
import time
lockA = threading.Lock()
lockB = threading.Lock()
# 面试官
def fool():
lockA.acquire() # 上锁
print("请解释什么是死锁")
time.sleep(1)
lockB.acquire() # 上锁
print("发 offer")
time.sleep(1)
lockA.release() # 解锁
lockB.release()
# 面试者
def user():
lockB.acquire()
print("请给我发 offer")
time.sleep(1)
lockA.acquire()
print("解释了什么是死锁")
time.sleep(1)
lockA.release()
lockB.release()
t1 = threading.Thread(target=fool)
t2 = threading.Thread(target=user)
t1.start()
t2.start()
4.递归锁
为了支持在同一线程中多次请求同一资源, Python 提供了 “可重入锁” : threading.RLock
。
RLock 内部维护着一个 Lock
和一个 **counter
**变量 , counter 记录了 acquire 的次数 , 从而使得资源可以被多次 **acquire** 。
提示
递归锁内部维护着一把 锁 和一个 计数器 每次 上锁,计数器 加 ,每次 解锁 ,计数器 减 计数器可以大于零也可以等于零,但不能 小于零
import threading
import time
lockR = threading.RLock()
def fool():
lockR.acquire()
print("请解释什么是死锁")
time.sleep(1)
lockR.acquire()
print("发 offer")
time.sleep(1)
lockR.release()
lockR.release()
def user():
lockR.acquire()
print("请给我发 offer")
time.sleep(1)
lockR.acquire()
print("解释了什么是死锁")
time.sleep(1)
lockR.release()
lockR.release()
t1 = threading.Thread(target=fool)
t2 = threading.Thread(target=user)
t1.start()
t2.start()