python多线程详解

1、多线程的概念

默认情况下,一个程序只有一个进程和一个线程,代码是一次线性执行的。而多线程则可以并发执行,效率要高。

1、使用threading.current_thread()可以看到当前线程的信息
2、使用threading.enumerate()函数可以看到当前的线程

代码范例:

import time
import threading
# 使用threading.current_thread()可以查看当前线程信息
# 使用threading.enumerate()可以看到当前线程
def coding():
	the_thread = threading.current_thread()
	for i in range(3):
		print('%s正在写第%d行代码'%(the_thread.name, i+1))
		time.sleep(1)


def drawing():
	the_thread = threading.current_thread()
	for i in range(3):
		print('%s正在画画'%(the_thread.name))
		time.sleep(1)


def multi_thread():
	# 创建线程
	th1 = threading.Thread(target=coding, name = '小明')
	th2 = threading.Thread(target=drawing, name = '小刚')

	# 线程开始
	th1.start()
	th2.start()

	print(threading.enumerate())


if __name__ == '__main__':
	multi_thread()

运行结果

在这里插入图片描述

为了让线程代码更好的封装,可以使用threading模块的Thread类,重写run方法,线程要执行的操作放在run函数体中,创建出线程对象

代码样例:

import time
import threading
from threading import Thread

# 创建写代码的类,做继承,重写run方法
class CodingThread(Thread):
	def run(self):
		thread1 = threading.current_thread()
		for i in range(3):
			print('%s正在写第%s行代码'%(thread1.name, i+1))
			time.sleep(1)

class DrawThread(Thread):
	def run(self):
		thread2 = threading.current_thread()
		for i in range(3):
			print('%s正在画第%s幅画'%(thread2.name, i+1))
			time.sleep(1)

def multi_thread():
	# 创建线程方法
	t1 = CodingThread()
	t2 = DrawThread()
	# 调用start()方法,开始线程
	t1.start()
	t2.start()

if __name__ == '__main__':
	multi_thread()

运行结果:
在这里插入图片描述

2、多线程共享全局变量的问题

多线程都是在同一个进程里面运行的,因此,进程中的全局变量所有线程皆可以共享。那么会有一个问题,线程执行的顺序是无序的,可能会造成数据错误

会出错的情况是什么样子呢,下面给一个例子
准备一个函数,这个函数的作用是将变量的值不断增加,
通过另外一个函数对这一个进程创建两个 线程,同时对num变量进行操作,当range()的范围是100的时候,结果是和代码预期的一致,分别是100和200,但是当这个数字变大之后,比如到达一千万,结果就不一样了,第一个循环是超出了一千万的结果,第二个循环是不够两千万

import threading

num = 0

def add_global_var():
	global num
	for i in range(100):
		num += 1
	print('num的值是%s'%num)

def multi_thread():
	for i in range(2):
		thread = threading.Thread(target = add_global_var)
		thread.start()

if __name__ == '__main__':
	multi_thread()

在这里插入图片描述
在这里插入图片描述

导致这样的情况出现的原因是什么呢,由于多个线程的顺序是不一样的,而且执行的时间不同,那么两个线程在处理num的值的时候,时间可能会先后,也有可能会同时到达,这样num的值就只会改变一次。

如何解决呢?那就需要在线程里面加入锁的机制了
threading提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他的线程就进不来了,直到当前线程处理完成之后,将锁释放,下一个线程才能处理

使用锁的原则:
1、把尽量少的和不耗时的代码放到锁中执行
2、代码执行完成之后要记得释放锁

加入之后的效果如下:

import threading

num = 0

gLock = threading.Lock()

def sub_global_var():
	global num
	# 在修改全局变量的操作上,加入锁的机制
	# 用完之后记得释放锁
	gLock.acquire()
	for i in range(10000000):
		num -= 1
	gLock.release()
	print('num的值是:', num)

def multi_thread():
	for i in range(3):
		t = threading.Thread(target = sub_global_var)
		t.start()

if __name__ == '__main__':
	multi_thread()

刚才的问题就得到了解决
在这里插入图片描述

3、生产者和消费者模型

生产者和消费者模型是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数据,将数据保存到一个中间变量中。消费者再从中间变量中取出数据进行消费。通过生产者和消费者模式,可以让代码实现高内聚低耦合的目标,分工更加明确,线程方便管理

举个例子,生产者负责生产苹果,消费者负责吃苹果,全局变量用来存储苹果的数量
代码如下

import threading, random, time
from threading import Thread
# 面向对象
gApple = 0
# 创建锁
gLock = threading.Lock()

# 创建生产者
class Producer(Thread):
	def run(self) -> None:
		global gApple
		while True:
			# 上锁
			gLock.acquire()
			apple = random.randint(1, 100)
			gApple += apple
			print('%s生产了%s个苹果,目前一共有%s个苹果'%(threading.current_thread().name, apple, gApple))
			# 释放锁
			gLock.release()
			time.sleep(1)

# 创建消费者
class Consumer(Thread):
	def run(self) -> None:
		global gApple
		while True:
			# 上锁
			gLock.acquire()
			apple = random.randint(1, 100)
			if gApple >= apple:
				gApple -= apple
				print('%s吃掉了%s个苹果,还剩下%s个苹果' % (threading.current_thread().name, apple , gApple))
			else:
				print('%s需要吃掉的苹果数量是%s,但是库存只有%s个了,消费失败'%(threading.current_thread().name, apple, gApple))
			# 释放锁
			gLock.release()
			time.sleep(1)

def multi_thread_ProducerAndConsumer():
	for i in range(5):
		# 创建生产者和消费者对象
		p = Producer()
		# 开始线程运行
		p.start()
	for i in range(5):
		c = Consumer()
		c.start()

if __name__ == '__main__':
	multi_thread_ProducerAndConsumer()

截取部分效果图:
在这里插入图片描述
上面的代码虽然实现了生产者和消费者模型,但是在上述条件中,程序并不能结束,一般情况是要有一个结束条件的,那么在这我们加入一些条件,让程序主动结束

import threading, random, time
from threading import Thread
# 面向对象
gApple = 0
# 创建锁
gLock = threading.Lock()
# 加入次数限制
gT = 0

# 创建生产者
class Producer(Thread):
	def run(self) -> None:
		global gApple, gT
		while True:
			# 上锁
			gLock.acquire()
			if gT >= 20:
				gLock.release()
				break
			apple = random.randint(1, 100)
			gApple += apple
			gT += 1
			print('%s生产了%s个苹果,目前一共有%s个苹果'%(threading.current_thread().name, apple, gApple))
			# 释放锁
			gLock.release()
			time.sleep(1)

# 创建消费者
class Consumer(Thread):
	def run(self) -> None:
		global gApple
		while True:
			# 上锁
			gLock.acquire()
			apple = random.randint(1, 100)
			if gApple >= apple:
				gApple -= apple
				print('%s吃掉了%s个苹果,还剩下%s个苹果' % (threading.current_thread().name, apple , gApple))
			else:
				if gT >= 20:
					gLock.release()
					break
				print('%s需要吃掉的苹果数量是%s,但是库存只有%s个了,消费失败'%(threading.current_thread().name, apple, gApple))
			# 释放锁
			gLock.release()
			time.sleep(1)

def multi_thread_ProducerAndConsumer():
	for i in range(5):
		# 创建生产者和消费者对象
		p = Producer(name = '生产者%d号'%(i+1))
		# 开始线程运行
		p.start()
	for i in range(5):
		c = Consumer(name = '消费者%d号'%(i+1))

		c.start()

if __name__ == '__main__':
	multi_thread_ProducerAndConsumer()

在这里插入图片描述

4、Condition版本的生产者消费者模型

Lock版本的生产者和消费者模式可以正常运行,但是存在一个不足,在消费者中,总是通过while循环并上锁的方式去判断条件是否正确。上锁也是一个很消耗CPU资源的行为,因此这种方式不是最好的,还有一种可以通过threading.Condition来实现。

Condition的具体函数

acquire上锁
release解锁
wait将当前线程处于等待状态,并且会释放锁。可以被其他线程使用notify和notify_all函数唤醒。被唤醒后会继续等待上锁,上锁后继续执行下面的代码
notify通知某个正在等待的线程,默认是第1个等待的线程
notify_all通知所有正在等待的线程。notify和notify_all不会释放锁。并且需要在release之前调用

在用Condition进行代码补充过程中,需要注意这几点。首先,在生产者类中规定了生产的次数,在进行生产的过程中,消费者消费的苹果数量可能会大于现有的苹果数量,那么此时消费者中的线程需要等待,等什么时候条件满足了,线程才会继续。因此,生产者中在生产了苹果之后,需要加入notify_all方法去唤醒等待的线程。

其次,在消费者消费的时候,要消费的苹果数量会有可能大于库存的苹果数量。因此,在这里需要一个条件的考虑。如果库存的数量小于要消费的数量时,需要做一个消费失败的提示,并让该线程等待,如果在该条件下,生产的次数已经达上限了,那也需要做具体相应的提示。之后满足库存苹果数量大于消费数量时,才能进行减法操作

import threading, random, time
from threading import Thread
# 面向对象
gApple = 0
# 创建锁
gCon = threading.Condition()
# 加入次数限制
gT = 0

# 创建生产者
class Producer(Thread):
	def run(self) -> None:
		global gApple, gT
		while True:
			# 上锁
			gCon.acquire()
			if gT >= 20:
				gCon.release()
				break
			apple = random.randint(1, 100)
			gApple += apple
			gT += 1
			print('%s生产了%s个苹果,目前一共有%s个苹果'%(threading.current_thread().name, apple, gApple))
			# 唤醒等待线程
			gCon.notify_all()
			# 释放锁
			gCon.release()
			time.sleep(1)

# 创建消费者
class Consumer(Thread):
	def run(self) -> None:
		global gApple
		while True:
			# 上锁
			gCon.acquire()
			apple = random.randint(1, 100)
			while gApple < apple:
				if gT >= 20:  # 如果生产者已经不生产,那么直接return返回
					print('%s想吃%s个苹果,但是只有%s个了,而且生产者不再生产'%(threading.current_thread().name, apple, gApple))
					return
				print('%s想吃%s苹果,但是只有%s个苹果了,失败!'%(threading.current_thread().name, apple, gApple))
				gCon.wait()
			gApple -= apple
			# 吃掉之后,打印信息
			print('%s吃了%s苹果,还有%s个苹果'%(threading.current_thread().name, apple, gApple))
			# 释放锁
			gCon.release()
			time.sleep(1)

def multi_thread_ProducerAndConsumer():
	for i in range(5):
		# 创建生产者和消费者对象
		p = Producer(name = '生产者%d号'%(i+1))
		# 开始线程运行
		p.start()
	for i in range(5):
		c = Consumer(name = '消费者%d号'%(i+1))

		c.start()

if __name__ == '__main__':
	multi_thread_ProducerAndConsumer()

运行结果

在这里插入图片描述

5、线程安全队列——Queue

在线程中,访问一些全局变量,加锁是一个经常的过程。如果要把一些数据存储到某个队列中,那python内置的线程安全模块:queue就能满足我们的需求。这个模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列,LIFO(后入先出)队列。这些队列都实现了锁原语(可以理解为原子操作,要么不做,要么就都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

相关函数如下:

Queue(maxsize)创建一个先进先出的队列
qsize()返回队列的大小
empty()判断队列是否为空
full()判断队列是否满了
get()从队列中取最后一个数据。默认情况下是阻塞的。也就是说如果队列已经空了,那么再调用就会一直阻塞,直到有新的数据添加进来。也可以使用'block = False',来关掉阻塞。如果关掉阻塞,在队列为空的情况下获取,就会抛出异常
put()将一个数据放到队列中。如果在队列满的情况下,再调用put()方法,也会阻塞,同样可以使用'block = False'关闭阻塞,来抛出异常

先了解相关方法:

import queue
from queue import Queue
# 创建队列并指定队列中的元素个数
q = Queue(5)
# put()  放置数据
for i in range(6):
	'''
	如果向队列中加入的数据,要多于创建队列时指定的元素个数
	调用put()方法时会报错,我们可以捕获这个异常,出现异常
	的时候就break,那么程序是可以正常放置数据的,只不过最
	多放置5个,在放置第6个的时候就捕捉到异常,直接break了
	block参数为False用于抛出异常
	'''
	try:
		q.put(i, block=False)
	except:
		break
print('队列大小', q.qsize())

# full()  判断队列是否为满
res = q.full()
print('队列是否满', res)

# get()  获取队列中的数据
for i in range(5):
	try:
		value = q.get(block=False)
	except:
		break
	print('队列中取数据', value)


# empty()  判断是否为空
# 上述代码已经取出过元素了,所以此时队列为空
res = q.empty()
print('判断队列是否为空', res)

在这里插入图片描述

结合Queue实现多线程

def add_num(q):
	'''
	:param q: 创建的队列
	:return:
	'''
	while True:
		n = random.randint(1, 100)
		q.put(n)
		print('向队列添加数字:', n)
		time.sleep(1)

def get_num(q):
	while True:
		print('从队列中取数字:', q.get())

def thread_queue():
	q = Queue(4)
	t1 = threading.Thread(target = add_num, args = [q])
	t2 = threading.Thread(target = get_num, args = [q])

	t1.start()
	t2.start()

if __name__ == '__main__':
	thread_queue()

这样的话就不需要上锁了,会依次执行
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值