一.库介绍
线程管理使用threading模块
threading模块提供的类: Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。
threading 模块提供的常用方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
threading 模块提供的常量:
threading.TIMEOUT_MAX 设置threading全局超时时间。
二.创建线程
2.1 Thread类常用属性和方法如下。
Thread类属性:
name:线程名
ident:线程的标识符
daemon:布尔值,表示这个线程是否是守护线程
Thread类方法:
__init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None):实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性
start():开始执行该线程
run():定义线程的方法。(通常开发者应该在子类中重写)
join(timeout=None):直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞
isAlive:布尔值,表示这个线程是否还存活(驼峰式命名,python2.6版本开始已被取代)
isDaemon():布尔值,表示是否是守护线程
setDaemon(布尔值):在线程start()之前调用,把线程的守护标识设定为指定的布尔值
2.2 目标函数作为参数的方式
import os
import time
import threading
def fun(n):
print('子线程开始运行……')
time.sleep(1)
my_thread_name = threading.current_thread().name#获取当前线程名称
my_thread_id = threading.current_thread().ident#获取当前线程id
print('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),n))
print('子线程运行结束……')
t = threading.Thread(target=fun , name='线程1',args=('参数1',))
t.start()
time.sleep(2)
main_thread_name = threading.current_thread().name#获取当前线程名称
main_thread_id = threading.current_thread().ident#获取当前线程id
print('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))
2.3 继承threading.thread的方式
import os
import time
import threading
class MyThread(threading.Thread):
def __init__(self , n , name=None):
super().__init__()
self.name = name
self.n = n
def run(self):
print('子线程开始运行……')
time.sleep(1)
my_thread_name = threading.current_thread().name#获取当前线程名称
my_thread_id = threading.current_thread().ident#获取当前线程id
print('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),self.n))
print('子线程运行结束……')
t = MyThread(name='线程1', n=1)
t.start()
time.sleep(2)
main_thread_name = threading.current_thread().name#获取当前线程名称
main_thread_id = threading.current_thread().ident#获取当前线程id
print('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))
三.常用方法与属性
3.1 守护线程:deamon
Thread类有一个名为deamon的属性,标志该线程是否为守护线程,默认值为False,当为设为True是表示设置为守护线程。是否是守护线程有什么区别呢?
当deamon值为True,即设为守护线程后,只要主线程结束了,无论子线程代码是否结束,都得跟着结束,这就是守护线程的特征。另外,修改deamon的值必须在线程start()方法调用之前,否则会报错。
可查看另一篇文章:python 线程(4)-- 守护线程与用户线程
3.2 join方法
join()方法的作用是在调用join()方法处,让所在线程(主线程)同步的等待被join的线程,等到join的线程结束后才执行当前所在线程
import threading
import time
def run():
time.sleep(2)
print('当前线程的名字是: ', threading.current_thread().name)
time.sleep(2)
if __name__ == '__main__':
start_time = time.time()
print('这是主线程:', threading.current_thread().name)
thread_list = []
for i in range(5):
t = threading.Thread(target=run)
thread_list.append(t)
for t in thread_list:
#t.setDaemon(True)
t.start()
for t in thread_list:
t.join()
print('主线程结束了!' , threading.current_thread().name)
print('一共用时:', time.time()-start_time)
》》》》
这是主线程: MainThread
当前线程的名字是: Thread-4
当前线程的名字是: Thread-3
当前线程的名字是: Thread-2
当前线程的名字是: Thread-1
当前线程的名字是: Thread-5
主线程结束了! MainThread
一共用时: 4.055499315261841
以上可看到:五个线程依次join,主线程(所在线程)等待他们结束才往下执行,而其他线程(同样调用join的线程)没有等待前面join的线程在正常执行。
四.线程同步
4.1 互斥锁 Lock
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源设置一个状态:锁定和非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
import time
import threading
def fun(lock):
re = lock.acquire(timeout = 3) #默认阻塞线程,直到超时
if not re:
print('get lock failed')
return
global num
temp = num
time.sleep(0.2)
temp -= 1
num = temp
lock.release()
print('主线程开始运行……')
t_lst = []
num =10 # 全局变量
lock = threading.Lock()
for i in range(10):
t = threading.Thread(target=fun , args=(lock,))
t_lst.append(t)
t.start()
[t.join() for t in t_lst]
print('num最后的值为:{}'.format(num))
print('主线程结束运行……')
死锁
使用Lock的时候必须注意是否会陷入死锁,所谓死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。关于死锁一个著名的模型是“科学家吃面”模型:
import time
from threading import Thread
from threading import Lock
def eatNoodles_1(noodle_lock, fork_lock, scientist):
noodle_lock.acquire()
print('{} 拿到了面'.format(scientist))
fork_lock.acquire()
print('{} 拿到了叉子'.format(scientist))
time.sleep(1)
print('{} 吃到了面'.format(scientist))
fork_lock.release()
noodle_lock.release()
print('{} 放下了面'.format(scientist))
print('{} 放下了叉子'.format(scientist))
def eatNoodles_2(noodle_lock, fork_lock, scientist):
fork_lock.acquire()
print('{} 拿到了叉子'.format(scientist))
noodle_lock.acquire()
print('{} 拿到了面'.format(scientist))
print('{} 吃到了面'.format(scientist))
noodle_lock.release()
print('{} 放下了面'.format(scientist))
fork_lock.release()
print('{} 放下了叉子'.format(scientist))
scientist_list1 = ['霍金','居里夫人']
scientist_list2 = ['爱因斯坦','富兰克林']
noodle_lock = Lock()
fork_lock = Lock()
for i in scientist_list1:
t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))
t.start()
for i in scientist_list2:
t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))
t.start()
》》
霍金 拿到了面
霍金 拿到了叉子
霍金 吃到了面
富兰克林 拿到了叉子居里夫人 拿到了面
霍金 放下了面
霍金 放下了叉子
#富兰克林和居里夫人一个拿了面等叉子,一个拿了叉子等面,进入死锁
4.2 递归锁:RLock
所谓的递归锁也被称为“锁中锁”,指一个线程可以多次申请同一把锁,但是不会造成死锁,这就可以用来解决上面的死锁问题。
import time
from threading import Thread
from threading import RLock
def eatNoodles_1(noodle_lock, fork_lock, scientist):
noodle_lock.acquire()
print('{} 拿到了面'.format(scientist))
fork_lock.acquire()
print('{} 拿到了叉子'.format(scientist))
time.sleep(1)
print('{} 吃到了面'.format(scientist))
fork_lock.release()
noodle_lock.release()
print('{} 放下了面'.format(scientist))
print('{} 放下了叉子'.format(scientist))
def eatNoodles_2(noodle_lock, fork_lock, scientist):
fork_lock.acquire()
print('{} 拿到了叉子'.format(scientist))
noodle_lock.acquire()
print('{} 拿到了面'.format(scientist))
print('{} 吃到了面'.format(scientist))
noodle_lock.release()
print('{} 放下了面'.format(scientist))
fork_lock.release()
print('{} 放下了叉子'.format(scientist))
scientist_list1 = ['霍金','居里夫人']
scientist_list2 = ['爱因斯坦','富兰克林']
noodle_lock=fork_lock = RLock()
for i in scientist_list1:
t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))
t.start()
for i in scientist_list2:
t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))
t.start()
上面代码可以正常运行到所有科学家吃完面条。
RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次
4.3 Condition
Condition可以认为是一把比Lock和RLOK更加高级的锁,其在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition内部常用方法如下:
1)acquire(): 上线程锁
2)release(): 释放锁
3)wait(timeout): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。
4)notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。
5)notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程
需要注意的是,notify()方法、notifyAll()方法只有在占用琐(acquire)之后才能调用,否则将会产生RuntimeError异常。
用Condition来实现生产者消费者模型:
import threading
import time
# 生产者
def produce(con):
# 锁定线程
global num
con.acquire()
print("工厂开始生产……")
while True:
num += 1
print("已生产商品数量:{}".format(num))
time.sleep(1)
if num >= 5:
print("商品数量达到5件,仓库饱满,停止生产……")
con.notify() # 唤醒消费者
con.wait()# 生产者自身陷入沉睡
# 释放锁
con.release()
# 消费者
def consumer(con):
con.acquire()
global num
print("消费者开始消费……")
while True:
num -= 1
print("剩余商品数量:{}".format(num))
time.sleep(2)
if num <= 0:
print("库存为0,通知工厂开始生产……")
con.notify() # 唤醒生产者线程
con.wait() # 消费者自身陷入沉睡
con.release()
con = threading.Condition()
num = 0
p = threading.Thread(target=produce , args=(con ,))
c = threading.Thread(target=consumer , args=(con ,))
p.start()
c.start()
》》
工厂开始生产……
已生产商品数量:1
已生产商品数量:2
已生产商品数量:3
已生产商品数量:4
已生产商品数量:5
商品数量达到5件,仓库饱满,停止生产……
消费者开始消费……
剩余商品数量:4
剩余商品数量:3
剩余商品数量:2
剩余商品数量:1
剩余商品数量:0
库存为0,通知工厂开始生产……
已生产商品数量:1
已生产商品数量:2
已生产商品数量:3
已生产商品数量:4
已生产商品数量:5
商品数量达到5件,仓库饱满,停止生产……
剩余商品数量:4
剩余商品数量:3
4.4 信号量:Semaphore
锁同时只允许一个线程更改数据,而信号量是同时允许一定数量的进程更改数据 。继续用上篇博文中用的的吃饭例子,加入有一下应用场景:有10个人吃饭,但只有一张餐桌,只允许做3个人,没上桌的人不允许吃饭,已上桌吃完饭离座之后,下面的人才能抢占桌子继续吃饭,如果不用信号量,肯定是10人一窝蜂一起吃饭:
from threading import Thread
import time
import random
from threading import Semaphore
def fun(i , sem):
sem.acquire()
print('{}号顾客上座,开始吃饭'.format(i))
time.sleep(random.random())
print('{}号顾客吃完饭了,离座'.format(i))
sem.release()
if __name__=='__main__':
sem = Semaphore(3)
for i in range(6):
p = Thread(target=fun, args=(i,sem))
p.start()
》》
0号顾客上座,开始吃饭
1号顾客上座,开始吃饭
2号顾客上座,开始吃饭
1号顾客吃完饭了,离座
3号顾客上座,开始吃饭
2号顾客吃完饭了,离座
4号顾客上座,开始吃饭
3号顾客吃完饭了,离座
5号顾客上座,开始吃饭
4号顾客吃完饭了,离座
0号顾客吃完饭了,离座
5号顾客吃完饭了,离座
五.定时器Timer
如果想要实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread类的一个子类。
如果是多长时间后只执行一次:
import threading
import time
def sayTime(name):
print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))
if __name__ == "__main__":
timer = threading.Timer(2.0, sayTime, ["Jane"])
timer.start()
输出结果:
你好,Jane为您报时,现在时间是:Thu Dec 6 15:03:41 2018
如果要每个多长时间执行一次:
import threading
import time
def sayTime(name):
print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))
global timer
timer = threading.Timer(3.0, sayTime, [name])
timer.start()
if __name__ == "__main__":
timer = threading.Timer(2.0, sayTime, ["Jane"])
timer.start()
参考: