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()
这个程序将红绿灯的情况来控制车的通行,即用红绿灯这线程来控制车线程,达到一个线程控制多个线程的目的。