Python基础之多线程
1.进程与线程
进程:一个正在执行的程序,称为一个进程
线程:进程执行的最小单位,可以保证进程的正常执行。一个进程中至少含有一个线程,该线程称为主线程
2.Python中的线程
python中使用_thread
或threading
模块对线程进程处理。其中threading
是对_thread
的再次整合,所以在Python3中建议使用threading
模块。
导入threading
模块
import threading
获取主线程
# 获取主线程
print(threading.main_thread())
# <_MainThread(MainThread, started 5748)>启动状态
创建子线程
# 线程执行的方法
def sum_num(n):
# 获取当前执行该任务的线程对象
th = threading.currentThread()
print("当前正在执行的线程名字为", th.name)
sum_re = 0
for i in range(n+1):
sum_re += n
print("求和结果:",sum_re)
# 创建子线程
# thread1 = Thread(target[绑定方法], args[参数列表(元组形式)],name[线程名称])
thread1 = Thread(target=sum_num, args=(1000000000,), name="thread1")
# 启动指定线程
thread1.start()
# setDaemon(True):如果设置为True,则是将该线程设置为主线程守护线程, 当主线程执行结束的时候,该线程不管任务是否完成都会被系统强制终止。thread1.setDaemon(True)的作用跟join的作用完全相反
thread1.setDaemon(True)
# join(timeout=time):设置主线程必须等待对应子线程的执行,等待指定的时间time之后,没如果子线程没有结果返回,主线程不再等待,继续执行。如果该时刻之内返回,主线程立刻继续执行,如果timeout设置为None(默认),此时主线程必须等待子线程运行结束才能执行
thread1.join(timeout=1)
3.锁(Lock)
当多个线程出现修改同一个公共资源的时候,为了防止多个线程同时争抢统一资源此时需要为给一个线程对应的操作执行上锁解锁任务
lock
对象中内置两个方法,分别是acquire
和release
,其中acquire()是获取锁的过程(上锁),release()
是释放锁的过程(解锁),但是注意如果程序运行过程中某一个线程在调用acquire()
上锁之后没有调用release()
解锁,此时就会出现死锁现象,此时程序会进入阻塞状态
from threading import Thread
import threading
# 创建lock对象
lock = threading.Lock()
# 定义全局变量
count = 0
# 定义函数,完成对公共资源修改操作
def change(n):
# 获取锁,并为当前线程上锁
lock.acquire()
global count
count += n
count -= n
# 为当前线程释放锁
lock.release()
# 定义函数完成对count进行n次修改
def target1(n):
for i in range(n):
change(i)
# 定义一个函数完成对count进行m次修改
def target2(m):
for i in range(m):
change(m)
# 创建两个子线程,分别完成target1任务和target2任务
thread1 = Thread(target=target1, args=(100,), name='thread1')
thread2 = Thread(target=target2, args=(100,), name='thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(count)
如果不使用Lock对象对临界资源进行上锁解锁,count值就会发生异常,随机会得到不同的数字。
注意:上锁解锁最好使用在临界区附近
4.递归锁(RLock)
先来测试关于Lock的一个小例子:
import threading
# 创建lock对象
lock = threading.Lock()
print("程序准备要锁")
lock.acquire()
print("程序上锁")
lock.acquire()
print("程序再次上锁")
lock.release()
lock.release()
print("程序结束")
Lock就像一个任性的大爷,当同一个线程申请两次acquire操作且中间没有执行release操作释放资源时,Lock大爷就会掀桌子,程序会陷入死锁状态,永远不会结束。
因此开发中为了避免该种情况,建议使用RLock(递归锁)
# 递归锁(相对于Lock更加高级的锁)
lock = threading.RLock()
print("程序准备要锁")
lock.acquire()
print("程序上锁")
lock.acquire()
print("程序再次上锁")
lock.release()
lock.release()
print("程序结束")
同样的程序,RLock就很人性化,当第一次申请上锁之后,再次检测到上锁操作时,RLock发现当前并没有可释放的资源并且发现请求者是同一个线程,此时RLock并不会像Lock一样直接掀桌子陷入死锁状态,而是直接略过该请求,直接执行之后的语句。
5.基于高级锁的线程处理机制(Condition)
Condition()
:threading内置的高级锁对象,内部默认封装的是一个RLock对象
Condition内部封装的也是acquire和release操作,只不过该操作内部也是通过RLock间接操作上锁和解锁过程。
同时condition内部封装了一个线程等待池,只要线程通过调用wait()
方法,此时线程会被自动丢到线程等待池中,直到另一个线程通过notify()
或者是notifyAll()
方法来唤醒等待池中的线程
Condition一般适用于两个线程互相协作完成的任务
注意:notify(n)
默认每次只唤醒一个线程,但是当指定n的值时,此时可以同时唤醒n个线程
简易捉迷藏例子:
import threading
import time
import random
con = threading.Condition()
# 该变量存储随机结果,判定谁输谁赢
result = 0
# 定义一个负责找的孩子
def seeker(name):
con.acquire()
print("我%s已经蒙好眼睛了,你可以去藏了!" % name)
con.wait()
for i in range(3):
print("%s is seeking" % name)
time.sleep(2)
global result
result = random.randint(0, 1)
if result == 0:
print("找到你了,我%s赢了!" % name)
else:
print("出来吧,我%s输了!" % name)
con.notify()
con.release()
# 定义另外一个孩子隐藏的过程
def hider(name):
con.acquire()
for i in range(3):
print("%s is hiding" % name)
time.sleep(2)
print("我%s已经藏好了,你来找我吧!" % name)
con.notify()
con.wait()
global result
if result == 0:
print("好可惜我%s输了!" % name)
else:
print("哈哈太好了我%s赢了!" % name)
con.release()
# 创建线程1执行seeker操作
thread1 = threading.Thread(target=seeker, args=("熊大",), name="thread1")
# 创建线程2执行hider操作
thread2 = threading.Thread(target=hider, args=("熊二",), name='thread2')
thread1.start()
thread2.start()
6.基于时间的线程处理机制(Event)
Event
:事件处理机制,全局定义了一个Flag,如果Flag值为False,那么当线程执行event.wait()
,此时该线程进入阻塞状态。但是如果Flag为True,此时线程调用event.wait()
,线程不会处于阻塞状态
clear()
:设置Flag为False
set()
:设置Flag为True
isset()
:判定当前Flag是否是True,默认状态下Flag为False
Event可以实现线程间的通信,使用Event可以使某一个线程处于等待状态(阻塞状态),等待其他线程通过set方法将Flag设置为True,此时所有等待状态的线程都会被唤醒
模拟红绿黄灯例子:
import threading
from threading import Event
import time, random
# 创建Event对象
event = Event()
# 模拟起始位置为红灯
print("起始为红灯状态")
# 创建红绿灯
def light():
countR = 60
countG = 25
countY = 3
# 记录当前灯的状态(0:红灯 1:绿灯 2:黄灯)
state = 0
while True:
if state == 0:
if event.is_set() == True:
event.clear()
time.sleep(1)
countR -= 1
if countR == 0:
print("红灯转成绿灯")
state = 1
countR = 10
elif state == 1:
if event.is_set() == False:
event.set()
time.sleep(1)
countG -= 1
if countG == 0:
print("绿灯转成黄灯")
state = 2
countG = 5
else:
time.sleep(1)
countY -= 1
if countY == 0:
print("黄灯转成红灯")
state = 0
countY = 2
# 定义函数完成车辆运行
def car_run():
while True:
# 模拟一辆车通过时间
time.sleep(random.randint(1, 3))
if event.is_set() == False:
event.wait()
else:
print("当前有一辆汽车通过")
# 定义两个线程分别完成红绿灯的控制和汽车的控制
thread1 = threading.Thread(target=light, name="thread1")
thread2 = threading.Thread(target=car_run, name="thread2")
thread1.start()
thread2.start()
7.基于计数器的线程处理机制(Semaphore和BoundedSemaphore)
(1)Semaphore
threading.Semaphore(n)
限制同一时间最多n个子线程同时运行
Semaphore
运行过程中,通过计数器完成线程的操作,Semaphore每一次调用acquire
,此时计数器-1;每一次调用release
,此时计数器+1。必须保证计数器的值比0大此时其他线程才能正常运行,否则线程其他处于阻塞状态。
模拟多个下载任务例子:
sem = threading.Semaphore(3)
def download():
sem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
sem.release()
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
(2)BoundedSemaphore
Semaphore
在使用过程中可以调用任意次release()
解锁操作,在计数器中就相当于可以多次进行+1操作,这便会导致系统中可处理的线程数越来越多,使锁机制名存实亡。
BoundedSemaphore
与Semaphore
类似,但BoundedSemaphore
会检查计数器内部的值,保证值不会大于初始设定的值n,如果超出n,就回引发ValueError错误。
同样是模拟多个下载任务例子:
bsem = threading.BoundedSemaphore(3)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
8.threading中获取线程数据
(1)返回当前存活的线程对象的数量 threading.activeCount()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 获取当前存活线程数量
print(threading.activeCount())
# 43
# 主线程+42个子线程
(2)返回当前线程对象 threading.currentThread()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 获取当前线程对象
print(threading.currentThread())
# <_MainThread(MainThread, started 5216)>
# 主线程
(3)返回当前存在的所有线程对象的列表 threading.enumerate()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
# 获取列表太长所以我减到了2个
for i in range(2):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 获取当前存活线程列表
print(threading.enumerate())
# [<_MainThread(MainThread, started 3788)>, <Thread(thread1, started 808)>, <Thread(thread2, started 3516)>]
(4)返回线程pid threading.get_ident()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 获取线程pid
print(threading.get_ident())
# 1376
(5)返回主线程对象 threading.main_thread()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("当前正在执行下载任务的线程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意这里的两个release操作会引发ValueError操作 正常操作请删除或注释一个
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 获取主线程对象
print(threading.main_thread())