python多线程学习笔记
摘自:[https://www.cnblogs.com/chenhuabin/p/10082249.html] BY 奥辰 感谢博主 💐
类的方式创建线程(面向对象编程)
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()))
互斥锁的死锁
所谓死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。关于死锁一个著名的模型是“科学家吃面”模型:
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()
输出结果:
霍金 拿到了面
霍金 拿到了叉子
霍金 吃到了面
霍金 放下了面
霍金 放下了叉子
爱因斯坦 拿到了叉子
居里夫人 拿到了面
霍金吃完后,爱因斯坦拿到了叉子,把叉子锁住了;居里夫人拿到了面,把面锁住了。爱因斯坦就想:居里夫人不给我面,我就吃不了面,所以我不给叉子。居里夫人就想:爱因斯坦不给我叉子我也吃不了面,我就不给叉子。所以就陷入了死循环。
为了解决Lock死锁的情况,就有了递归锁:RLock。
RLock
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次aquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁.即多个线程同时执行,多个线程获得多把钥匙,究竟哪一个线程先开锁?这要取决于哪一个线程最先获得第一把锁的钥匙(如下例小明先拿到了筷子锁🔒)。当且仅当一个线程上面的锁全部打开后(小明进程释放了锁和筷子),才能轮到拿着下一把钥匙的进程开锁
from threading import Thread, Lock, RLock
import time
def eat1(args, Chopsticks_lock, Noodles_lock):
Chopsticks_lock.acquire()
print('%s拿到了筷子'%args)
time.sleep(1.5)
Noodles_lock.acquire()
print('%s拿到了面条'%args)
print('%s吃到了面条'%args)
Noodles_lock.release()
Chopsticks_lock.release()
def eat2(args, Chopsticks_lock, Noodles_lock):
Noodles_lock.acquire()
print('%s拿到了面条' % args)
time.sleep(1)
Chopsticks_lock.acquire()
print('%s拿到了筷子'%args)
print('%s吃到了面条'%args)
Noodles_lock.release()
Chopsticks_lock.release()
Chopsticks_lock = Noodles_lock = RLock()
Thread(target=eat1, args=('小明', Chopsticks_lock, Noodles_lock)).start()
Thread(target=eat2, args=('小红', Chopsticks_lock, Noodles_lock)).start()
Thread(target=eat1, args=('小兰', Chopsticks_lock, Noodles_lock)).start()
Thread(target=eat2, args=('小军', Chopsticks_lock, Noodles_lock)).start()
结果输出:
小明拿到了筷子
小明拿到了面条
小明吃到了面条
小红拿到了面条
小红拿到了筷子
小红吃到了面条
。。。
可以推测RLock是以进程为单位的,哪一个进程先上了第一个锁,哪一个进程就可以畅通无阻,拿到该线程上面的所有锁的钥匙。
遥控锁——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()
```
#信号量Semaphore(n)
**同时允许一定数量的进程更改数据 **
```python
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(10):
p = Thread(target=fun, args=(i,sem))
p.start()
```
#事件:Event(起到一个通讯作用)
如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时候就可以用threading为我们提供的Event对象,Event对象主要有一下几个方法:
isSet():返回event的状态值;
wait():如果 isSet()==False【默认状态】,将阻塞线程;
set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态(取消阻塞状态), 等待操作系统调度;
clear():恢复event的状态值为False。
#定时器:Timer
Timer是Thread类的一个子类。
如果想要实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。
```python
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()
```
#线程池
在我们上面执行多个任务时,使用的线程方案都是“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。所以就有了线程池的诞生,
由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。
```python
from concurrent.futures import ThreadPoolExecutor
import time
def func(n):
time.sleep(2)
print(n)
return n*n
t = ThreadPoolExecutor(max_workers=5) # 最好不要超过CPU核数的5倍
t_lst = []
for i in range(20):
r = t.submit(func , 1)#执行任务,传递参数
t_lst.append(r.result())#获取任务返回结果
t.shutdown()#相当于close() + join()
print(t_lst)
print('主线程运行结束……')
```
#队列 Queue
python中Queue模块提供了队列都实现了锁原语,是线程安全的,能够在多线程中直接使用。Queue中的队列包括以下三种:
1)FIFO(先进先出)队列, 第一加入队列的任务, 被第一个取出;
2)LIFO(后进先出)队列,最后加入队列的任务, 被第一个取出;
3)PriorityQueue(优先级)队列, 保持队列数据有序, 最小值被先取出。
Queue模块中的常用方法如下:
qsize() 返回队列的规模
empty() 如果队列为空,返回True,否则False
full() 如果队列满了,返回True,否则False
**get([block[, timeout]])获取队列,timeout等待时间**
get_nowait() 相当get(False)
**put(item) 写入队列,timeout等待时间,如果队列已满再调用该方法会阻塞线程**
put_nowait(item) 相当put(item, False)
**task_done() 在完成一项工作之后,task_done()函数向任务已经完成的队列发送一个信号**
join() 实际上意味着等到队列为空,再执行别的操作。
```python
import queue
import threading
def fun():
while True:
try:
data = q.get(block = True, timeout = 1) #不设置阻塞的话会一直去尝试获取资源
except queue.Empty:
print(' {}结束……'.format(threading.current_thread().name))
break
print(' {}取得数据:{}'.format(threading.current_thread().name , data))
q.task_done()
print(' {}结束……'.format(threading.current_thread().name))
print("主线程开始运行……")
q = queue.Queue(5)
#往队列里面放5个数
for i in range(5):
q.put(i)
for i in range(0, 3):
t = threading.Thread(target=fun , name='线程'+str(i))
t.start()
q.join() #等待所有的队列资源都用完
print("主线程结束运行……")
```
输出结果:
主线程开始运行……
线程0取得数据:0
线程0结束……
线程0取得数据:1
线程0结束……
线程1取得数据:2
线程0取得数据:3
线程0结束……
线程0取得数据:4
线程0结束……
线程1结束……
主线程结束运行……
线程1结束……
线程2结束……
线程0结束……