python多线程

本文深入探讨Python多线程的实现方式,包括thread和threading模块的使用,多线程创建与管理,以及锁机制、condition类的wait和notify方法详解。通过生产者消费者模型,阐述线程间同步与通信的原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python多线程及notify和wait使用

python多线程

python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python由于GIL(global interpreter lock 全局解释锁)的存在无法使用threading充分利用CPU资源,GIL使得python同一个时刻只有一个线程在一个cpu上执行字节码,并且无法将多个线程映射到多个cpu上,即不能发挥多个cpu的优势。即多线程只能在一个CPU上执行,CPU是按照时间片轮询的方式来执行子线程的。

多线程创建方式

  1. 使用Thread类进行创建
from threading import Thread
t = Thread(target=function_name, args=(parameter1, parameterN))
t.start()

function_name为启动线程的名字,parameter1, parameterN为对应的参数

  1. 直接继承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()
  1. 使用Thread中Timer创建循环定时线程
import threading
def heartBeat(self):
	heartThread = thrreading.Timer(5,heartBeat)

多线程管理

  1. 锁机制
    由于线程之间随机调度:某线程可能在执行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()
	
  1. 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()
			
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值