多线程,协程
互斥锁解决共享数据同步问题
- from threading import Lock
- 创建锁
- l = Lock()
- 上锁
- l.acquire()
- 解锁
- l.release()
import time
from threading import Lock
tickets = 1000
l = Lock()
def sale(name):
global tickets
while tickets > 0:
#上锁
l.acquire()
if tickets > 0:
tickets -= 1
time.sleep(0.01)
print('{}售出1张票,剩余{}张'.format(name,tickets))
#解锁
l.release()
t1 = threading.Thread(target=sale,args=('窗口1',))
t2 = threading.Thread(target=sale,args=('窗口2',))
t1.start()
t2.start()
- 工作过程
- 当一个线程调用锁的 acquire()方法获得锁时,锁就进入“locked”状态。 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就 会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的 release()方法释 放锁之后,锁进入“unlocked”状态。
- 总结:
- 好处:
- 保护共享数据完整
- 坏处:
- 降低执行效率
- 有可能导致死锁
- 好处:
死锁(了解)
-
在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁现象。 如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套
-
示例
- 有两个人分别做“西兰花”和“红烧肉”,每个人都需要“锅”和“铲子”才能炒菜
-
import threading,time class XiLanHua_Thread(threading.Thread): def run(self): if mutex_C.acquire(): print(self.name + "拿到了铲子") if mutex_G.acquire(): print(self.name + "拿到了锅") mutex_G.release() mutex_C.release() print(self.name+"使用完成了!") class HongShaoRou_Thread(threading.Thread): def run(self): if mutex_G.acquire(): print(self.name + "拿到了锅") #表示使用资源消耗的时间 time.sleep(1) if mutex_C.acquire(): print(self.name+"拿到了铲子") mutex_C.release() mutex_G.release() print(self.name +"使用完了") if __name__ == "__main__": mutex_C = threading.Lock() mutex_G = threading.Lock() t1 = XiLanHua_Thread() t2 = HongShaoRou_Thread() t1.start() t2.start()
-
解决方案
#如果锁着,那就算了 if lock2.acquire(blocking = False) #给你2秒钟时间,把东西给我,不然我就不要了 if lock2.acquire(timeout=2)
生产者消费者问题
- Python提供了queue这一线程安全的容器,可以方便的和多线程结合起来。 queue包括FIFO先入先出队列Queue,LIFO后入先出队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步
-
示例
from threading import Thread from queue import Queue import time q = Queue(maxsize=10) class Produce(Thread): count = 1 def __init__(self,name): super(Produce, self).__init__() self.name = name def run(self): while True: result = '{0}生产的第{1}个包子'.format(self.name,Produce.count) q.put(result) print(result) Produce.count += 1 time.sleep(1) class Custom(Thread): def __init__(self,name): super(Custom, self).__init__() self.name = name def run(self): while True: if q.qsize() > 0: r = q.get() print('{0}吃了{1}'.format(self.name, r)) time.sleep(1) if __name__ == "__main__": t1 = Produce('张三') t2 = Custom('李四') t3 = Custom('王五') t1.start() t2.start() t3.start()
threadlocal的使用(了解)
import threading
# 创建全局ThreadLocal对象:
local_var = threading.local()
def process_student():
print(local_var.a)
def process_thread(v):
# 绑定ThreadLocal的student:
local_var.a = v
process_student()
t1 = threading.Thread(target= process_thread, args=('123',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('456',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
- 总结:
- 全局变量 local_var 就是一个 ThreadLocal 对象,每个 Thread 对它都可以读写 v 属性,但互不影响。你可以把 local_var看成全局变量,但每个属性如local_var.v 都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。可以理解为全局变量 local_var是一个dict,不但可以用local_var.v,还可以绑定其他变量,如local_var.b。ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源
全局解释器锁
- 概述:
- python 代码的执行是由 python 虚拟机(又名解释器主循环)进行控制的。python在设计时是这样考虑的,在主循环中同时只能有一个控制线程在执行,就像单CPU系统中的多进程一样。内存中可以有许多程序,但是在任意给定时刻只能有一个程序在运行。同理,尽管 python 解释器中可以运行多个线程,但是在任意给定时刻只有一个线程会被解释器执行
- CPU密集型操作的效率问题
- 单线程耗时
def task1(): start_time = time.time() j = 0 for i in range(10000000): j += 1 end_time = time.time() print('耗时:{}'.format(end_time-start_time))
- 多线程耗时
def task2(threadName): j = 0 start_time = time.time() for i in range(5000000): j += 1 end_time = time.time() print('耗时:{}'.format(end_time - start_time)) for i in range(2): t = threading.Thread(target=task2,args=('线程{}'.format(i),)) t.start()
- 多进程耗时
if __name__ == '__main__': p = multiprocessing.Pool(2) for i in range(2): p.apply_async(task2, args=('进程{}'.format(i),)) p.close() p.join()
协程的使用
- 比线程更小的执行单元
- 某个函数, 可以在任何地方保存当前函数的一些临时变量等信息, 然后切换到另外一个函数中执行
- 协程的切换只是单纯的操作CPU的上下文,比线程的切换更快速
- 1:N 模式。 所谓 1:N 就是一个线程作为一个容器可以放置多个协程
- 协程的使用方式:yield
- 使用greenlet完成协程
from greenlet import greenlet
def test1():
print(12)
gr2.switch()
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch()#切换
print(78)
gr1 = greenlet(test1) #启动一个携程
gr2 = greenlet(test2)
gr1.switch()
- 使用gevent完成协程
import gevent
def task(n):
for i in range(n):
print(gevent.getcurrent(),i)
gevent.sleep(1)
#创建协程操作
g1 = gevent.spawn(task,5)
g2 = gevent.spawn(task,5)
g3 = gevent.spawn(task,5)
# g1.join()
# g2.join()
# g3.join()
gevent.joinall([
g1,g2,g3
])