1. 多线程的基本使用
importthreadingimporttimedefrun(num):print('Num: %s'%num)
time.sleep(3)if num == 4:print('Thread is finished.')
# 对函数 run 创建5个线程for i in range(5):#创建线程,target:目标函数,args:函数的参数,使用元组的形式
t = threading.Thread(target=run,args=(i,))#开始线程活动
t.start()
time.sleep(0.01)print('Main thread is finished.')
结果:
Num: 0
Num:1Num:2Num:3Num:4Main thread is finished.
Thread is finished.
# 上面打印的顺序是先打印Main thread is finished.因为主线程已经完成了,而子线程里 slee(3),所以此时子线程尚未完成,大约3秒后,才打印的 Thread is finished.
PS. 线程类的使用
importthreadingimporttime
num=0
lock= threading.Lock() #互斥锁,因为多个线程要对共享的数据:num 进行修改,防止出现错误。
classMyThread(threading.Thread): # 继承线程类def __init__(self):
super().__init__() #写了__init__,就一定要继承父类的构造方法。父类的__init__的参数是默认参数,故可以不用重写父类的参数。
def task(self): #自定义的函数
globalnumif lock.acquire(): #如果上锁成功,则执行:
num+=1
print(f'{self.name}, Num is : {num}') # 打印线程名和num的值
lock.release()#释放锁,让数据供其他线程使用
time.sleep(1)deftask2(self):
time.sleep(0.1)print(f'{self.name}','Task2 is being called.')
time.sleep(1)def run(self): #重写父类的run方法。每个单独的线程控制中,start()会自动调用 run()方法。
self.task()
self.task2()for i in range(10):
T= MyThread() #针对每个线程对象,start()最多只能调用一次,否则会抛出错误。所以才每循环一次,在循环内部重新生成一个对象
T.start()
2. 等待子线程执行:join
importthreadingimporttimedefrun(num):print('Num: %s'%num)
time.sleep(3)if num == 4:print('Thread is finished.')
Tlist= []for i in range(5):#创建线程,target:目标函数,args:函数的参数,使用元组的形式
t = threading.Thread(target=run,args=(i,))#开始线程活动
t.start()
Tlist.append(t)for t in Tlist: #针对每个子线程,等待子线程执行一段时间,再继续往下执行主线程。
#t.join(0.1) # 针对每个子线程,等待0.1秒,继续执行主线程
t.join() #没写时间,则默认等待子线程执行完毕,才继续往下执行主线程。
time.sleep(0.01)print('Main thread is finished.')
结果:
Num: 0
Num:1Num:2Num:3Num:4Thread is finished.
Main thread is finished.
# 子线程使用了join方法,等待子线程执行完毕才继续执行主线程,所以打印顺序是正常的。
3. 守护线程
即只要主线程执行完毕,不管子线程状态如何,都会被强制杀掉。
importthreadingimporttimedefrun(num):print('Num: %s'%num)
time.sleep(3)if num == 4:print('Thread is finished.')
Tlist=[]for i in range(5):#创建线程,target:目标函数,args:函数的参数,使用元组的形式
t = threading.Thread(target=run,args=(i,))#设置为守护线程
t.setDaemon(True)#开始线程活动
t.start()
Tlist.append(t)for t in Tlist: #针对每个子线程,等待子线程执行一段时间,再继续往下执行主线程。
t.join(0.1) #对子线程等待0.1秒后,继续执行主线程
#t.join() # 没写时间,则默认等待子线程执行完毕,才继续往下执行主线程。
time.sleep(0.01)print('Main thread is finished.')print('Current Thread:',threading.activeCount()) # 打印当前活动线程数量,这是主线程最后一个语句,所以不管此时还有多少个活动的守护线程,当此句执行完毕,守护线程都会被杀掉。
结果:
Num: 0
Num:1Num:2Num:3Num:4Main thread isfinished.
Current Thread:6
# 少打印了Thread is finished. 因为 主线程结束后,子线程就被杀掉了。
4. 线程锁
GIL锁: Global Interpreter Lock,全局解释器锁。为了解决多线程之间数据完整性和状态同步的问题,在任意时刻只有一个线程在解释器中运行。这是CPython中的问题。
线程安全:在多线程中,共享的数据同一时间只能由一个线程执行。
互斥锁:(threading.Lock) 并不关心当前是哪个线程占有了该锁;如果该锁已经被占有了,那么任何其它尝试获取该锁的线程都会被阻塞,包括已经占有该锁的线程也会被阻塞。
递归锁:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源(已经占用该锁的线程,可以多次actuire,并不会阻塞)
死锁:死锁就是当一个线程已经占用了锁并且尚未释放,但是又有新的获取该锁的请求,亦或着两个线程分别占据着对方想要获取的锁,此时线程进入了阻塞的状态,就是死锁。
e.g.
互斥锁:threading.Lock
importthreadingclassMyThread(threading.Thread):def run(self): #这是重写的父类的run方法
globalnumif mutex.acquire(): #判断能否加锁,acquire()返回的是加锁的状态,加锁成功就是True
num = num+1msg= self.name+'set num to'+str(num)print(msg)#mutex.acquire() # 这两句话如果不注释掉,就会进入死锁的状态。因为上一个锁还未释放,再次请求加锁,就会阻塞。
#mutex.release()
mutex.release() #释放锁
num =0
mutex=threading.Lock()deftest():for i in range(5):
t=MyThread()
t.start()if __name__ == '__main__':
test()
普通方法:
importthreadingimporttimedefrun(n):globalnumif mutex.acquire(): #返回获取锁的结果,加锁成功为True,失败false
num = num+1msg='Thread-%s set num to' %n +str(num)print(msg)
mutex.release()
time.sleep(2)
num=0
mutex=threading.Lock()for i in range(5):
t= threading.Thread(target=run,args=(i+1,))
t.start()
递归锁:threading.RLock
importthreadingclassMyThread(threading.Thread):defrun(self):globalnumifmutex.acquire():
num= num+1msg= self.name+'set num to'+str(num)print(msg)
mutex.acquire()#虽然再次acquire,但是并不会阻塞
mutex.release()
mutex.release()
num=0
mutex=threading.RLock()deftest():for i in range(5):
t=MyThread()
t.start()if __name__ == '__main__':
test()
5. 信号量 semaphore
信号量用于限制同时运行的线程数量
importthreadingimporttimedefrun(n):
sema.acquire()print('Thread: %s' %n)
time.sleep(2)
sema.release()
# 声明信号量,同时允许5个线程执行
sema= threading.BoundedSemaphore(5)for i in range(20):
t= threading.Thread(target=run,args=(i+1,))
t.start()
6. 事件:Event
事件可以用于多个线程进行简单通讯,事件可以控制一个内部 flag,初始时是 False,通过 set 方法使其变成 True,clear 方法使其变成 False,flag 为 False 时 wait 方法会一直阻塞直到flag 为True。
importthreadingimporttime#事件可以控制一个内部标志,通过set方法使其变成True,clear方法使其变成False,为False时wait方法会一直阻塞直到标志为True。#这个标志初始时是False
event=threading.Event()#event.set() # 设置flag为True#event.clear() # 设置flag为False#event.wait() # 一直等待(阻塞),直到有线程使用 set 方法将 flag 变成 True#event.is_set() # 返回flag的值
defboss():print('Boss: 今天加班5秒钟')
event.clear() # 清除 flag,使其值变为 false
time.sleep(5)print("Boss: 时间到了,大家回家吧")
event.set() # 设置 flag,使其值变为 Truedefemployee():whileTrue:ifevent.is_set(): # 判断:如果 flag 为 Trueprint('Employee: 回家了!')break
else:print('Emplyee: 工作中,请勿打扰...')
event.wait() # 如果 flag 为 False,则使用 wait 方法一直等待,直到有线程将其设置为 True
# 创建两个线程,分别为boss和employee,让它们根据 event 进行简单的通讯
b= threading.Thread(target=boss,)
e= threading.Thread(target=employee,)
b.start()
e.start()
结果:
Boss: 今天加班5秒钟
Emplyee: 工作中,请勿打扰...
Boss: 时间到了,大家回家吧
Employee: 回家了!