Python多线程

一、线程概念

在程序运行时,操作系统会创建一个进程,并且会创建一个线程,这个线程就是主线程,主线程可以创建子线程。线程看上去同时运行,其实是按照并发执行的,走走停停,一直到所有形线程完成为止。线程像进程一样会有生命周期,如下所示:

将程序进行多线程编程,其性能会得到很大提升。python线程对CPU密集型性能提高不大,对I/O密集型性能提高很大。

二、多线程示例

import threading
import time

class myThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		
	def run(self):
		out()     #加入执行的程序代码
		
def out():
	print_time1()
	print_time2()

def print_time1():
	print("Locka is acquired")
	print("Lockb is acwuired")
	
def print_time2():
	print("Lockb is acquired")
	time.sleep(2)
	print("Locka is acwuired")

def main():
	for i in range(50):
		t = myThread()
		t.start()        #执行线程

main()

以上就是线程的简单程序,我们创建了50个线程,将他们同时运行。他们完成的时间不一样,其先后顺序也不能确定。使用方法就是自己写一个类,继承threading.Thread类,并重写方法run(),将自己要运行的程序放入run()函数之中就行。
但是,上述程序有一个问题,就是在调用函数out()时,可能在一个线程还没有执行完时,就暂停,CPU转而去执行另一个线程,导致另一个线程修改了这个线程的数据,导致输出错误的结果。解决办法就是在同一时刻就只能有一个线程访问临界资源,其他线程只能等待。

三、线程同步

在python中实现线程同步有多种方法

1. 线程锁(Lock)

GIL(全局解释器锁)

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL。以下是原理图:

我们对临界资源加上锁,这样其他线程就无法访问,直到这个线程完成操作,释放线程锁之后为止。如下代码:

import threading
import time
#创建锁
locka = threading.Lock() 
lockb = threading.Lock() 

class myThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		
	def run(self):
		out()

def out():
	print_time1()
	print_time2()

def print_time1():
	locka.acquire()  #获取锁
	print("Locka is acquired")
	lockb.acquire()  #获取锁
	print("Lockb is acwuired")
	lockb.release()  #释放锁
	locka.release()  #释放锁

def print_time2():
	lockb.acquire()
	print("Lockb is acquired")
	time.sleep(2)
	locka.acquire()
	print("Locka is acwuired")
	locka.release()
	lockb.release()

def main():
	for i in range(50):
		t = myThread()
		t.start()

main()

在上面程序中,我们创建了两个锁locka和lockb,分别对临界资源加锁,这样就可以让同一时刻就只有一个线程执行,避免输出错误结果。但是上述代码还有一个错误,当第一个线程执行到函数print_time2()的time_sleep(2)时,需要获取锁locka,但是locka已经被第二个线程获取,还没有释放,而且第二个线程也需要获取lockb才能继续运行,但是lockb已被第一个线程获取,还没有释放,就这样,两个线程会一直等待,陷入死锁。解决办法是引入可重入锁。

2.递归锁(RLock)

递归锁就是在同一个线程中可以获取锁所多次,不会陷入死锁。但是在acquire()n次之后,需要release()n次。

import threading
import time

rlock = threading.RLock()   #RLock本身有一个计数器,如果碰到acquire,那么计数器+1
							#如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1

class myThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		
	def run(self):
		out()

def out():
	print_time1()
	print_time2()

def print_time1():
	rlock.acquire()  #获取锁
	print("Locka is acquired")
	rlock.acquire()  #获取锁
	print("Lockb is acwuired")
	rlock.release()  #释放锁
	rlock.release()  #释放锁

def print_time2():
	rlock.acquire()
	print("Lockb is acquired")
	time.sleep(2)
	rlock.acquire()
	print("Locka is acwuired")
	rlock.release()
	rlock.release()

def main():
	for i in range(50):
		t = myThread()
		t.start()

main()

三、Semaphore(信号量)

threading模块里的Semaphore类实现了信号量对象,可用于控制获取资源的线程数量。所具有的acquire()和release()方法,可以用with语句的上下文管理器。当进入时,将调用acquire()方法,当退出时,将调用release()。

import threading
import time

sem = threading.Semaphore(3)   #设置线程并发数

class myThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		
	def run(self):
		out()

def out():
	print_time1()
	print_time2()

def print_time1():
	sem.acquire()    #线程数减一
	print("Locka is acquired")
	print("Lockb is acwuired")
	sem.release()    #线程数加一

def print_time2():
	sem.acquire()   #线程数减一
	print("Lockb is acquired")
	print("Locka is acwuired")
	sem.release()   #线程数加一

def main():
	for i in range(10):
		t = myThread()
		t.start()

main()

四、Condition(条件变量)

Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将默认生成一个RLock实例。

可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

Condition():

  • acquire(): 线程锁
  • release(): 释放锁
  • wait(timeout): 线程挂起,并释放锁,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。
  • notify(n=1): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 最多唤醒n个等待的线程。
  • notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

以下就以生产者消费者为例:

import threading
import time
import random

con = threading.Condition()

class Goods():
	def __init__(self):
		self.__goods = 0
	
	def getgoods(self):
		return self.__goods

	def add(self):
		self.__goods += 1
	
	def sub(self):
		self.__goods -= 1

	def isEmpty(self):
		if self.__goods <= 0:
			return True
		else:
			return False

	def isFull(self):
		if self.__goods >= 10:
			return True
		else:
			return False

class Producer(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			con.acquire()			#获取锁
			while goods.isFull():   #货物满了,需要消费才能生产,进入阻塞
				con.wait()
			goods.add()				#生产一件货物
			print("生产一件货物,总货物数量为:", goods.getgoods())
			con.notifyAll()			#生产一件货物后便唤醒所有正在等待的消费者
			con.release()			#释放锁
			time.sleep(random.random())

class Consumer(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			con.acquire()			#获取锁
			while goods.isEmpty():	#货物消费完了,需要生产货物,进入阻塞
				con.wait()
			goods.sub()				#消费一件货物
			print("消费一件货物,总货物数量为:", goods.getgoods())
			con.notifyAll()			#消费一件货物后便唤醒所有正在等待的生产者
			con.release()			#释放锁
			time.sleep(random.random())

goods = Goods()

def main():
	threads = []
	#threads.append(Producer())
	#threads.append(Comsumer())

	for i in range(5):
		threads.append(Producer())
	
	for i in range(5):
		threads.append(Consumer())
	
	for th in threads:
		th.start()

main()

五、同步队列

让我们考虑更复杂的一种场景:产品是各不相同的。这时只记录一个数量就不够了,还需要记录每个产品的细节。很容易想到需要用一个容器将这些产品记录下来。

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

用FIFO队列实现上述生产者与消费者问题的代码如下:

import threading
import time
import random
import queue

q = queue.Queue()    #创建一个线程同步队列

class Producer(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			item = random.randint(0, 16)
			while q.qsize() >= 10:
				pass
			q.put(item)    #添加货物
			print("生产货物%02d, 队列大小:%02d" % (item, q.qsize()))
			time.sleep(random.randint(0, 3))

class Consumer(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			item = q.get()   #消费货物,若为空,会阻塞
			print("消费货物%02d, 队列大小:%02d" % (item, q.qsize()))
			time.sleep(random.randint(0, 3))

def main():
	threads = []
	
	for i in range(5):
		threads.append(Producer())
	
	for i in range(5):
		threads.append(Consumer())
	
	for th in threads:
		th.start()

main()

六、Event(事件)

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait、clear、set。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • event.set() 设置标志位为True
  • event.clear() 清空标志位,标志位为False
  • event.wait() 等待设置标志位,阻塞
  • event.isSet() 判断标志位是True还是False

下面就采用红绿灯车通行的例子来示例:

import threading
import time

event = threading.Event() 	


def Lighter():     
	event.set()
	count = 0
	while True: 
		if count > 5 and count <= 10: #红灯
			event.clear() #清除标志位
		elif count > 10:  #变为绿灯
			event.set()  #重新设置标志位
			count = 0
		time.sleep(1) 
		count += 1

def Car(name):
	while True:
		if event.isSet():  #判断标志位为True
			print("light is green, %d is running" % name)
			time.sleep(2)
		else:
			print("light is red, %d is waiting" % name)
			event.wait()   #阻塞,停车


def main():
	light = threading.Thread(target=Lighter) 
	light.start() 
	
	threads = []
	for i in range(5):    #开五部车
		threads.append(threading.Thread(target=Car, args=(i,)))
		
	for car in threads:
		car.start()


main()

这个程序将红绿灯的情况来控制车的通行,即用红绿灯这线程来控制车线程,达到一个线程控制多个线程的目的。

posted @ 2019-01-28 21:16  你好,果果  Views(554)  Comments(0Edit  收藏  举报