[toc]
python基础二十五 并发编程-多线程
1.线程概念
线程:实现多任务的另一种方式,一个进程中同时运行的多个子任务就成为线程
轻量级进程:线程又被称为轻量级进程,是更小的执行单元
- 一个进程可拥有多个并行的线程,当中每一个线程,共享当前进程的资源
- 一个进程中的线程共享相同的内存单元/内存地址空间,这样就可以访问相同的变量和对象,而且他们从同一堆中分配对象,进行通信、数据交换、同步操作
- 线程间的通信是在同一个地址上进行的,所以不需要额外的通信机制,这就使得通信更简单而且信息传递速度也更快
线程的5种状态
- 多线程程序的执行顺序是不确定的(操作系统决定)。当执行到sleep语句时,线 程将被阻塞(Blocked) , 到sleep结束后, 线程进入就绪(Runnable) 状态, 等待调度。 而线程调度将自行选择一个线程执行。 代码中只能保证每个线程都运行 完整个run函数, 但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定
1、新状态
:线程对象已经创建,还没有在其上调用start()方法。
2、可运行状态
:当线 程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态
:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4、等待/阻塞/睡眠状态
:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的(可运行的),但是当前没有条件运行。 但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态
:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程,线程一旦死亡,就不能再次执行,如果在一个死去的线程上调用start()方法,会抛出异常
2.线程和进程的区别
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可以看成是轻量级的进程,同一个类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务 | 在同一个应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存,线程使用的资源是它所属的进程的资源,线程只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一个线程,而是多个线程完成的 | 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量进程 |
3.多线程实现
3.1 通过threading.Thread直接在线程中运行函数
//创建单线程
import threading #导入threading模块
def thread():
print("线程启动")
t = threading.Thread(target=thread) #创建线程
t.start() #启动线程
//利用for循环创建多线程
import threading
def thread():
print("子线程启动")
for i in range(5):
t = threading.Thread(target=thread)
t.start()
结果:
子线程启动
子 线程启动
子线程启动
子线程启动
子线程启动
3.2 通过类继承threading.Thread类来创建线程
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(5):
print(i)
t1 = MyThread()
t2 = MyThread()
t1.start()
t2.start()
结果:
0
1
2
3
4
0
1
2
3
4
3.3 查看当前线程数量
len(threading.enumerate()
import threading
import time
import random
def func():
time.sleep(random.randint(1, 3))
print(threading.current_thread().name,f"当前活跃线程数量{len(threading.enumerate())}")
lst = [threading.Thread(target=func,name=f"线程{i}") for i in range(10)]
for i in lst:
i.start()
结果: 线程名称不固定
线程2 当前活跃线程数量11
线程8 当前活跃线程数量10
线程7 当前活跃线程数量9
线程4 当前活跃线程数量8
线程3 当前活跃线程数量7
线程1 当前活跃线程数量6
线程0 当前活跃线程数量5
线程9 当前活跃线程数量4
线程6 当前活跃线程数量3
线程5 当前活跃线程数量2
4.线程同步
4.1 互斥锁
创建锁
- lock = threading.Lock()
锁定
- lock.acquire()
释放锁
- lock.release()
未加锁之前循环100万次出现的结果不准确BUG
from multiprocessing import Queue,Process,Lock
import threading
num = 0
def f1():
global num
for i in range(1000000):
num += 1
print(num)
def f2():
global num
for i in range(1000000):
num += 1
print(num)
t1 = threading.Thread(target=f1)
t2 = threading.Thread(target=f2)
t1.start()
t2.start()
print("主线程的num是",num)
结果1:
主线程的num是 299428
1226857
1536106
结果2:
主线程的num是 263111
1173618
1499331
每一次执行的结果都不一样
加锁解决循环100万次出现结果不准确的BUG
from multiprocessing import Queue,Process,Lock
import threading
num = 0
def f1():
global num
lock.acquire()
for i in range(1000000):
num += 1
print(num)
lock.release()
def f2():
global num
lock.acquire()
for i in range(1000000):
num += 1
print(num)
lock.release()
lock = threading.Lock() #创建锁
t1 = threading.Thread(target=f1)
t2 = threading.Thread(target=f2)
t1.start()
t2.start()
print("主线程的num是",num)
结果1:
主线程的num是 235674
1000000
2000000
结果2:
主线程的num是 227114
1000000
2000000
主线程的num还是不一致,因为线程间数据是共享的,锁只能保证f1、f2两个函数中完整执行
4.2 互斥锁实现线程同步
代码整体思路
1.创建3个线程,分别执行3个函数f1、f2、f3
2.创建3个锁,只有在锁1创建后不上锁,锁2、锁3都上锁
3.由于锁1没有上锁,因此可以先执行,锁1中执行后释放锁2,锁2执行,执行后释放锁3,锁3执行,执行完后释放锁1,锁1执行,这样就可以无限循环顺序执行锁1、锁2、锁3
import threading
from multiprocessing import Lock
def f1():
while True:
lock1.acquire() #抢锁1
print(1)
lock2.release() #释放锁2,这样就能执行函数f2
def f2():
while True:
lock2.acquire() #抢锁2
print(2)
lock3.release() #释放锁3,这样就能执行函数f3
def f3():
while True:
lock3.acquire() #抢锁3
print(3)
lock1.release() #释放锁1,这样就能执行函数f1
#创建3个线程,并分别执行函数f1、f2、f3
t1 = threading.Thread(target=f1)
t2 = threading.Thread(target=f2)
t3 = threading.Thread(target=f3)
#创建3个锁,并且锁2、锁3创建后就上锁
lock1 = threading.Lock()
lock2 = threading.Lock()
lock2.acquire()
lock3 = threading.Lock()
lock3.acquire()
#启动线程
t1.start()
t2.start()
t3.start()
结果:
1
2
3
1
2
3
。。。