Python并发编程基础 №⑥ 线程队列、守护线程、条件和定时器、线程池、线程的事件和信号量、线程锁
1、线程队列
在线程中也可以使用队列传递数据,原因是它自带锁,方便,保证数据安全!
1 # 1、队列 2 # 在线程中也可以使用队列传递数据,原因是它自带锁,方便,保证数据安全!不用每 3 q = queue.Queue() 4 q.put(1) 5 q.put(2) 6 q.put(3) 7 print(q.get()) # 队列的特点:先进先出。 8 9 10 # 2.栈 性质的队列 11 lifo = queue.LifoQueue() 12 lifo.put(1) 13 lifo.put(2) 14 print(lifo.get()) # 栈的特点:先进后出 15 16 17 # 3.带优先级的队列 18 pq = queue.PriorityQueue() 19 pq.put((-1, 'a')) 20 pq.put((1, 'b')) 21 pq.put((-1, 'c')) 22 23 24 print(pq.get()) # 先比较数字,然后比较后面内容的ascii码,数字一样的话,码小的输出! #(0, 'a')
2、守护线程
1 from threading import Thread 2 import time 3 4 def func(): 5 while True: 6 time.sleep(1) 7 print('***'*3) 8 9 def call(): 10 time.sleep(5) 11 print('call in ....') 12 13 thread = Thread(target=func,) 14 thread.daemon = True # 开启该子线程的守护线程模式:它会随着主线程的运行完成而结束 15 thread.start() 16 t = Thread(target=call,) 17 t.start() 18 print('主线程代码执行完毕!') # 如果主线程下的其他非守护线程没结束,它也不结束 19 20 # 对比守护进程:守护进程会随着主进程的代码执行完毕,而结束!
3、线程锁
import time, random from threading import Lock, Thread # 这个Lock跟进程中的锁,都是互斥锁。存在如果同时有多把锁的话,有可能产生死锁的问题! # Lock 互斥锁 total = 10 def func(lock): global total # lock.acquire() tmp = total time.sleep(1) # 放在此处才有效果,10减10次后不是0,数据不安全,虽然cpython计时器中有GIL锁, # 同一时刻只能让一个线程访问cpu,但是此时刚好cpu时间片到了,必须释放,其他线程于是可以继续访问的了@ 所以还得加锁,保护数据! tmp -= 1 total = tmp # lock.release() lock = Lock() t_lst = [] for i in range(10): t = Thread(target=func, args=(lock, )) t.start() t_lst.append(t) [t.join() for t in t_lst] # 加锁会等10秒,才会有结果! 效率降低,但是数据安全 print(total) # 典型死锁例子:科学家们吃意面 ''' 有多位科学家在一起聚餐,可是桌子上只有一盘面和一个叉子;他们又很饿,于是他们开始抢了, 只有同时抢到叉子和盘子的人,才能吃上面!可是大概率的发生的却是:一位抢到叉子,另外一位抢到盘子,都吃不上面了!''' # 同步锁或互斥锁 # fork_lock = Lock() # disk_lock = Lock() # 递归锁 from threading import RLock fork_lock = disk_lock = RLock() def eat_noodle(name): disk_lock.acquire() # 与delay中的一定要相反,先拿到盘子 print(name, '拿到了盘子') fork_lock.acquire() print(name, '拿到了叉子') print(' is eating delicious Italy Noodles') fork_lock.release() disk_lock.release() def eat_noodle_delay(name): fork_lock.acquire() # 与上面的函数相反,先拿叉子,这样才会有死锁发生! print(name, '拿到了叉子') time.sleep(random.randint(1, 3)) # 有延时 disk_lock.acquire() print(name, '拿到了盘子') print(name, ' is eating delicious Italy Noodles') disk_lock.release() fork_lock.release() Thread(target=eat_noodle, args=('tom', )).start() Thread(target=eat_noodle_delay, args=('jack',)).start() Thread(target=eat_noodle, args=('trump',)).start() Thread(target=eat_noodle_delay, args=('steven',)).start() # 解决死锁的好办法:用递归锁 from threading import RLock rlock = RLock() rlock.acquire() rlock.acquire() print('rlock is here') # 不同于互斥锁,可以执行此句! rlock.release() rlock.release() # 所以用递归锁可以解决上面的死锁问题, # 直接将两把锁都用递归锁 # fork_lock = disk_lock = RLock() # 一个钥匙串上的两把钥匙
4、条件:Condition 特殊的锁
1 from threading import Condition, Thread 2 3 # acquire release 4 # 一个条件被创建之初 默认有一个False状态 5 # False状态 会影响wait一直处于等待状态 6 # notify(int数据类型) 造钥匙 7 8 9 def func(index, condition): 10 condition.acquire() 11 condition.wait() # 等钥匙 12 print('在第%s次循环里' % index) 13 condition.release() 14 15 16 condition = Condition() # 默认有一个递归锁 17 18 for i in range(12): 19 Thread(target=func, args=(i, condition)).start() 20 21 while 1: 22 try: 23 num = int(input('>>>').strip()) 24 condition.acquire() 25 condition.notify(num) # 制造3把一次性的锁 26 condition.release() 27 except ValueError: 28 print('请输入整数!') 29 continue
5、定时器 延迟启动的线程
1 import time 2 from threading import Timer 3 4 # Timer是继承Thread类的,因此它也可以实现多线程,是延时启动的线程 5 6 7 def sync_time(): 8 print('与格林威治时间同步!') 9 10 11 while True: 12 t = Timer(2, sync_time) 13 t.start() 14 time.sleep(2) # 此处不等待2秒,子线程会在等待2秒后不断输出!因为上述代码是不阻塞的,异步进行的!
6、线程池
1 from concurrent.futures import ThreadPoolExecutor 2 import time 3 from concurrent.futures import ProcessPoolExecutor 4 5 6 def run(): 7 time.sleep(2) 8 print(' i am running ') 9 10 11 def func(num): 12 time.sleep(1) 13 ret = num + 1 14 # print(ret) 15 return ret 16 17 18 def deal(ret): 19 print('-------', ret.result()) 20 21 22 pool = ThreadPoolExecutor(max_workers=5) # 默认 不要超过cpu个数*5 23 24 25 pool.map(run, range(20)) # 没有返回值 26 27 t_lst = [] 28 for i in range(20): 29 t = pool.submit(func, i) 30 t_lst.append(t) 31 32 # pool.shutdown() 33 34 for t in t_lst: 35 print(t.result()) # 一次性拿返回值浪费时间,可以随时取,即不关闭池即可实现! 36 37 # 回调函数 38 for i in range(20): 39 t = pool.submit(func, i).add_done_callback(deal) 40 41 42 # 2.线程池ProcessPoolExecutor的用法同上 43 p_pool = ProcessPoolExecutor(max_workers=7) # 默认比核数多1 44 '''形同代码就省略了'''
7、线程的事件 Event
1 import time 2 import random 3 from threading import Thread 4 from threading import Event 5 6 # 复习在进程中事件: 7 # 创建的是带状态的:False 8 # 如果此时使用wait方法,就会阻塞! 9 # 判断状态的方法:is_set() 10 # 转换状态的方法:set() 设为True clear()设为False 11 12 # 进程中举的例子是交通灯 13 # 线程中不重复了,就举连接数据的例子,一个线程连接,一个线程检查是否连接成功 14 15 16 def check_conn(event): 17 time.sleep(random.randint(4, 6)) 18 event.set() 19 20 21 def conn_db(event): 22 count = 0 23 while count < 5: 24 time.sleep(0.1) 25 e.wait(1) 26 if event.is_set(): 27 print('连接数据库成功!') 28 break 29 else: 30 count += 1 31 print('第%d次,连接数据库不成功!' % count) 32 else: 33 raise TimeoutError('连接数据库超时!') 34 35 36 e = Event() 37 38 t1 = Thread(target=check_conn, args=(e, )) 39 t2 = Thread(target=conn_db, args=(e, )) 40 41 t1.start() 42 t2.start()
8、线程的信号量 Semaphore
1 import time 2 import random 3 from threading import Semaphore, Thread 4 # 回顾进程中的Semaphore,会联想到进ktv的例子,一次只能同时让N个人进去! 5 6 7 def enter_ktv(semaphore, name): 8 time.sleep(random.randint(1, 3)) 9 semaphore.acquire() 10 print(name, 'is singing!') 11 semaphore.release() 12 13 14 sp = Semaphore(5) # 一次可以允许5个线程访问函数中的print 15 16 for i in range(20): 17 t = Thread(target=enter_ktv, args=(sp, i)) 18 t.start()

浙公网安备 33010602011771号