python基础(锁机制,守护线程,线程队列,线程池)
一、 互斥锁(Lock)与递归锁(RLock)机制
1.1 由互斥锁(Lock)产生的死锁现象:
# 互斥锁(死锁现象): # 死锁现象: from threading import Lock lock = Lock() lock.acquire() print(123) lock.acquire() # 等待获取锁(死锁状态) print(456) lock.release() # 等待释放锁 lock.release() print('程序结束!!!') # 科学家吃面产生的死锁现象: from threading import Lock, Thread import time noodle_lock = Lock() # 面条锁 fork_lock = Lock() # 叉子锁 def eat1(name): noodle_lock.acquire() time.sleep(0.5) print('%s拿到面了' % name) fork_lock.acquire() print('%s拿到叉子了' % name) print('%s吃面' % name) fork_lock.release() print('%s放下叉子了' % name) noodle_lock.release() print('%s放下面了' % name) def eat2(name): fork_lock.acquire() print('%s拿到叉子了' % name) noodle_lock.acquire() print('%s拿到面了' % name) print('%s吃面' % name) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) # 三个线程(科学家)需同时获取叉子和面条才能吃到面条 Thread(target=eat1, args=('Annie',)).start() Thread(target=eat2, args=('Lisa',)).start() Thread(target=eat1, args=('Jane',)).start()
1.2 解决由互斥锁产生的死锁,可重入锁(递归锁:RLock)解决方法:
# 递归锁(可重入锁): # 方式一: # RLock:解决死锁问题(建议少用递归锁,一般程序的递归锁的出现都是程序设计不合理导致): from threading import RLock, Thread import time fork_lock = noodle_lock = RLock() def eat1(name): noodle_lock.acquire() # 获取递归锁(进入第一层) print('%s拿到面了' % name) time.sleep(0.5) fork_lock.acquire() # 获取递归锁(进入第二层) print('%s拿到叉子了' % name) print('%s吃面' % name) fork_lock.release() # 释放递归锁(出第二层) print('%s放下叉子了' % name) noodle_lock.release() # 释放递归锁(出第一层) print('%s放下面了' % name) def eat2(name): fork_lock.acquire() print('%s拿到叉子了' % name) noodle_lock.acquire() print('%s拿到面了' % name) print('%s吃面' % name) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) Thread(target=eat1, args=('Annie',)).start() # 线程一 Thread(target=eat2, args=('Lisa',)).start() # 线程二 Thread(target=eat1, args=('Jane',)).start() # 线程三 # 方式二: # 直接使用互斥锁结果科学家吃面问题: from threading import Lock, Thread lock = Lock() def eat1(name): lock.acquire() # 获取锁 print('%s拿到面了' % name) print('%s拿到叉子了' % name) print('%s吃面' % name) print('%s放下叉子了' % name) print('%s放下面了' % name) lock.release() # 释放锁 def eat2(name): lock.acquire() print('%s拿到叉子了' % name) print('%s拿到面了' % name) print('%s吃面' % name) print('%s放下面了' % name) print('%s放下叉子了' % name) lock.release() Thread(target=eat1, args=('Annie',)).start() # 线程一 Thread(target=eat2, args=('Lisa',)).start() # 线程二 Thread(target=eat1, args=('Jane',)).start() # 线程三
1.3 总结:
gil锁机制:保证线程同一时刻只能一个线程访问CPU,不可能有两个线程同时在CPU上执行指令
lock锁机制:保证某一段代码 在没有执行完毕之后,不可能有另一个线程也执行这一段代码
二、 线程锁实现
为什么要使用线程锁:
1. 同一时刻同一段代码,只能有一个进程来执行这段代码
2. 锁的应用场景,当多个进程需要操作同一个文件/数据库的时候 ,
3. 会产生数据不安全,我们应该使用锁来避免多个进程同时修改一个文件
示例:
# 示例: import json import time from multiprocessing import Process, Lock # 查询余票 def search_ticket(name): with open('ticket', encoding='utf-8') as f: dic = json.load(f) print('%s查询余票为%s' % (name, dic['count'])) # 购买车票 def buy_ticket(name): with open('ticket', encoding='utf-8') as f: dic = json.load(f) time.sleep(2) if dic['count'] >= 1: print('%s买到票了' % name) dic['count'] -= 1 time.sleep(2) with open('ticket', mode='w', encoding='utf-8') as f: json.dump(dic, f) else: print('余票为0,%s没买到票' % name) # 使用线程锁方式一: def use1(name, lock): search_ticket(name) print('%s在等待' % name) lock.acquire() # 获取锁 print('%s开始执行了' % name) buy_ticket(name) lock.release() # 释放锁 # 使用线程锁方式二: def use2(name, lock): """ # with lock: # 代码块 # 上下文管理:在__enter__方法中获取锁(acquire),在__exit__方法中释放锁(release) """ search_ticket(name) print('%s在等待' % name) with lock: # 获取锁 + 释放锁 print('%s开始执行了' % name) buy_ticket(name) if __name__ == '__main__': lock = Lock() l = ['alex', 'wusir', 'baoyuan', 'taibai'] for name in l: Process(target=use1, args=(name, lock)).start() # 方式一 Process(target=use2, args=(name, lock)).start() # 方式二
三、 队列(Queue,LifoQueue,PriorityQueue)的使用
3.1 先进先出队列(Queue):
# 先进先出队列 # Queue就是一个线程队列的类,自带lock锁,实现了线程安全的数据类型 from queue import Queue q = Queue() q.put({1, 2, 3}) q.put_nowait('abc') print(q.get_nowait()) # 获取一个值,如果没有抛出异常 print(q.get()) q.empty() # 判断是否为空 q.full() # 判断是否为满 q.qsize() # 查看队列的大小
3.2 先进后出队列(栈:LifoQueue):
# 先进后出的队列(last in first out): # 线程安全的队列 栈和后进先出的场景都可以用 from queue import LifoQueue lfq = LifoQueue() lfq.put(1) lfq.put('abc') lfq.put({'1', '2'}) print(lfq.get()) # {'2', '1'} print(lfq.get()) # abc print(lfq.get()) # 1
3.3 优先级队列(PriorityQueue):
# 优先级队列: from queue import PriorityQueue pq = PriorityQueue() pq.put((10, 'aaa')) pq.put((2, 'bbb')) pq.put((20, 'ccc')) print(pq.get()) # 最想获取到优先级最高的2,以元组形式返回 (2, 'bbb') print(pq.get()) # (10, 'aaa') print(pq.get()) # (20, 'ccc')
四、线程队列(生产者与消费者模型)
# 生产者与消费者示例: import time import random from queue import Queue from threading import Thread # 生产者 def producer(q): for i in range(10): time.sleep(random.random()) food = 'Spam %s' % i print('%s生产了%s' % ('Jane', food)) q.put(food) # 消费者 def consumer(q, name): while True: food = q.get() # food = 食物/None if not food: break # 当消费者消费完成后,最后拿到None时,退出消费者程序 time.sleep(random.uniform(1, 2)) print('%s 吃了 %s' % (name, food)) if __name__ == '__main__': q = Queue() p1 = Thread(target=producer, args=(q,)) # 生产者 p1.start() c1 = Thread(target=consumer, args=(q, 'Lisa')) # 消费者 c1.start() c2 = Thread(target=consumer, args=(q, 'Annie')) # 消费者 c2.start() p1.join() q.put(None) # 生产者完成生产后,队列最后加入None,表示已经生产完成 q.put(None) # 每个消费者需要None退出程序
五、守护线程:
5.1 守护线程的定义:
1. 主线程会等待子线程的结束而结束
2. 守护线程会守护主线程和所有的子线程
3. 守护线程会随着主线程的结束而结束,主线程结束,进程资源回收,守护线程被销毁
示例:
# 守护线程示例: import time from threading import Thread # 守护线程 def daemon_func(): while True: time.sleep(0.5) print('守护线程') # 其他子线程 def son_func(): print('start son') time.sleep(5) print('end son') t = Thread(target=daemon_func) # 开启守护线程 t.daemon = True t.start() Thread(target=son_func).start() # 开启son_func子线程 time.sleep(3) print('主线程结束') # 主线程代码结束后,等待子线程son_func结束
总结:
守护进程 :只会守护到主进程的代码结束
守护线程 :会守护所有其他非守护线程的结束
六、线程池
线程池模块与进程池共用同一个模块:concureent.futures
示例:
# 线程池: from urllib.request import urlopen from concurrent.futures import ThreadPoolExecutor # 导入线程池类 # 获取网页 def get_html(name, addr): ret = urlopen(addr) return {'name': name, 'content': ret.read()} # 保存数据 def cache_page(ret_obj): dic = ret_obj.result() with open(dic['name'] + '.html', 'wb') as f: f.write(dic['content']) url_dic = { '协程': 'http://www.cnblogs.com/Eva-J/articles/8324673.html', '线程': 'http://www.cnblogs.com/Eva-J/articles/8306047.html', '目录': 'https://www.cnblogs.com/Eva-J/p/7277026.html', '百度': 'http://www.baidu.com', 'sogou': 'http://www.sogou.com' } # 创建20个线程 # 方式一: t = ThreadPoolExecutor(20) # 实例化线程池对象 for url in url_dic: task = t.submit(get_html, url, url_dic[url]) # 提交线程任务 task.add_done_callback(cache_page) # 函数回调,数据保存 # 方式二: with ThreadPoolExecutor(20) as t: # 上下文管理方式实现实例化的线程池 for url in url_dic: task = t.submit(get_html, url, url_dic[url]) task.add_done_callback(cache_page)
https://www.cnblogs.com/WiseAdministrator/