python多线程
python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python由于GIL(global interpreter lock 全局解释锁)的存在无法使用threading充分利用CPU资源,GIL使得python同一个时刻只有一个线程在一个cpu上执行字节码,并且无法将多个线程映射到多个cpu上,即不能发挥多个cpu的优势。即多线程只能在一个CPU上执行,CPU是按照时间片轮询的方式来执行子线程的。
多线程创建方式
- 使用Thread类进行创建
from threading import Thread
t = Thread(target=function_name, args=(parameter1, parameterN))
t.start()
function_name为启动线程的名字,parameter1, parameterN为对应的参数
- 直接继承Thread类进行创建
from threading import Thread
# 创建一个类,必须要继承Thread
class MyThread(Thread):
# 继承Thread的类,需要实现run方法,线程就是从这个方法开始的
def run(self):
# 具体的逻辑
function_name(self.parameter1)
def __init__(self, parameter1):
# 需要执行父类的初始化方法
Thread.__init__(self)
# 如果有参数,可以封装在类里面
self.parameter1 = parameter1
# 如果有参数,实例化的时候需要把参数传递过去
t = MyThread(parameter1)
# 同样使用start()来启动线程
t.start()
- 使用Thread中Timer创建循环定时线程
import threading
def heartBeat(self):
heartThread = thrreading.Timer(5,heartBeat)
多线程管理
- 锁机制
由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。
Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。
可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。
实现方法:
acquire(): 尝试获得锁定。使线程进入同步阻塞状态。
release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。
以更新订单簿和删除订单簿为例,更新和删除相同orderBookId下的订单簿操作,需要在更新和删除进行加锁处理,保证在同一时刻只能进行更新或者删除操作。
但不同的orderBookId下的订单簿更新和删除是可以同步进行的,所以加锁的维度是给每一个orderBookId分配锁,而不是所有的orderBookId共享一个锁;
import threading
class OrderBookManger(object):
def __init__(self):
self.__lockManger = {}
def addOrderBook(self,orderBookId):
self.__lockManger[orderBookId] = threading.LOCK()
def update(self,orderBookId):
self.__lockManger[orderBookId].acquire()
##to do somthing
self.self.__lockManger[orderBookId].release()
def delete(self,orderBookId):
self.__lockManger[orderBookId].acquire()
##to do somthing
self.self.__lockManger[orderBookId].release()
- condition类及wait和notify方法
当小伙伴a在往火锅里面添加鱼丸,这个就是生产者行为;另外一个小伙伴b在吃掉鱼丸就是消费者行为。当火锅里面鱼丸达到一定数量加满后b才能吃,这就是一种条件判断了,需要用到condition类。
除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。
condition类包含的操作:
acquire(): 线程锁
release(): 释放锁
wait(timeout): 线程挂起,并且释放锁,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。
notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。
notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程
正常唤醒场景:当a同学往火锅里面添加鱼丸加满后(最多5个,加满后通知b去吃掉),通知b同学去吃掉鱼丸(吃到0的时候通知a同学继续添加)
##生产者消费者举例
# coding=utf-8
import threading
import time
con = threading.Condition()
num = 0
# 生产者
class Producer(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
# 锁定线程
global num
con.acquire() ##锁定线程,消费者代码无法同步运行
while True:
print "开始添加!!!" #<2>
num += 1
print "火锅里面鱼丸个数:%s" % str(num)
time.sleep(1)
if num >= 5:
print "火锅里面里面鱼丸数量已经到达5个,无法添加了!"
# 唤醒处于等待的线程,消费者线程会被唤醒<1>代码处,但notify不释放锁,所以此时消费者还未拿到锁
con.notify()
# 等待通知,并释放锁,则消费者拿到锁,开始运行<1>处代码
con.wait()
# 释放锁
con.release()
# 消费者
class Consumers(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
con.acquire()
global num
while True:
print "开始吃啦!!!" ##<1>
num -= 1
print "火锅里面剩余鱼丸数量:%s" %str(num)
time.sleep(2)
if num <= 0:
print "锅底没货了,赶紧加鱼丸吧!"
con.notify() # 唤醒生产者线程<2>
# 等待通知,释放锁,开始运行生产者<2>处代码
con.wait()
con.release()
p = Producer()
c = Consumers()
p.start()
c.start()
上述情况,生产者和消费者在同一个锁定池中,当生产者获取锁之后,消费者代码不能执行,直到生产者释放锁之后才能进行;需要注意的是notify不会释放锁,一定要在notify之后增加释放锁的操作。
异常情况:唤醒和等待动作处于不同的锁定池中,会出现无法唤醒wait状态线程。下述场景fetch操作从队列中取数据当队列为空时进行fetch操作进行等待挂起;put操作往队列中添加数据
import threading,queue
class BaseStrategy():
def __init__(self):
self.quote_td = threading.Thread(target=self.fetch)
self.cond = threading.condition()
elf.quote_td.start()
self.msg_queue = queue.Queue()
def fetch(self):
while(true):
self.cond.acquire()
if self.msg_Queue.size() == 0: ##(2)
self.cond.wait() ##(4)
one = self.msg_queue.get() ##(3)
obj = BaseStrategy()
def putone():
while(True):
if obj.msg_queue.size()==0:
obj.msg_queue.put("33)
obj.cond.acquire()
obj.cond.notify()
obj.cond.release()
else: ##(3)
obj.msg_queue.put("34)
putone()
上述代码中由于putone和fetch不在一个锁定池中,条件变量只是针对线程msg_queue,所以putone 和 fetch 会有交替执行情况,当putone函数执行到(3)时,fetch拿到执行权,执行了(3)之后,循环继续执行(2),通过语句(4)把自己挂起,这时putone拿到执行权,继续从(3)开始执行,循环后会一直走(3),就会导致fetch线程一直不会被notify。
解决方案:目的是当队列为空时进行等待挂起,所以直接使用了队列的get(timeout=5)方法,python队列是线程安全的,每次从队列中获取数据时如果队列有数据则直接获取,否则等待5s钟,仍拿不到数据则报异常。修改后的代码如下
import threading,queue,Queue
class BaseStrategy():
def __init__(self):
self.quote_td = threading.Thread(target=self.fetch)
self.cond = threading.condition()
elf.quote_td.start()
self.msg_queue = queue.Queue()
def fetch(self):
while(true):
try:
one = self.msg_queue.get(timeout=5)
except Queue.Empty:
print("empty queue")
continue
obj = BaseStrategy()
def putone():
while(True):
obj.msg_queue.put("34)
putone()