多线程(threading module)
一、线程与进程
线程定义:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程定义:An executing instance of a program is called a process.(程序的执行实例称为进程。)
线程与进程的区别:
1. 线程共享创建它的进程的地址空间; 进程有自己的地址空间。
2. 线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
3. 线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
4. 新线程很容易创建; 新流程需要复制父流程。
5. 线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
6. 对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。
二、Python GIL(Global Interpreter Lock)
--> 全局解释器锁 :在同一时刻,只能有一个线程进入解释器。
三、threading 模块
3.1 线程的2种调用方式
直接调用
1 import threading 2 import time 3 4 def sayhi(num): #定义每个线程要运行的函数 5 6 print("running on number:%s" %num) 7 8 time.sleep(3) 9 10 if __name__ == '__main__': 11 12 t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 13 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 14 15 t1.start() #启动线程 16 t2.start() #启动另一个线程 17 18 print(t1.getName()) #获取线程名 19 print(t2.getName())
继承式调用
1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self,num): 7 threading.Thread.__init__(self) 8 self.num = num 9 10 def run(self):#定义每个线程要运行的函数 11 12 print("running on number:%s" %self.num) 13 14 time.sleep(3) 15 16 if __name__ == '__main__': 17 18 t1 = MyThread(1) 19 t2 = MyThread(2) 20 t1.start() 21 t2.start()
3.2 常用方法(Join/Daemon)
join() --> 在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
setDaemon(True) --> 将线程声明为守护线程,必须在start() 方法调用之前设置。当守护线程运行时间超过主线程时,守护线程将随主线程结束而结束。
其他方法:
1 threading 模块提供的其他方法: 2 # threading.currentThread(): 返回当前的线程变量。 3 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 4 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 5 # 除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法: 6 # run(): 用以表示线程活动的方法。 7 # start():启动线程活动。 8 # join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 9 # isAlive(): 返回线程是否活动的。 10 # getName(): 返回线程名。 11 # setName(): 设置线程名。
3.3 同步锁(Lock)
r = threading.Lock() r.acquire() --> 加锁 r.release() --> 解锁
1 import time 2 import threading 3 4 def addNum(): 5 global num #在每个线程中都获取这个全局变量 6 # num-=1 7 lock.acquire() 8 temp=num 9 print('--get num:',num ) 10 #time.sleep(0.1) 11 num =temp-1 #对此公共变量进行-1操作 12 lock.release() 13 14 num = 100 #设定一个共享变量 15 thread_list = [] 16 lock=threading.Lock() 17 18 for i in range(100): 19 t = threading.Thread(target=addNum) 20 t.start() 21 thread_list.append(t) 22 23 for t in thread_list: #等待所有线程执行完毕 24 t.join() 25 26 print('final num:', num )
3.4 线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
1 import threading,time 2 3 class myThread(threading.Thread): 4 def doA(self): 5 lockA.acquire() 6 print(self.name,"gotlockA",time.ctime()) 7 time.sleep(3) 8 lockB.acquire() 9 print(self.name,"gotlockB",time.ctime()) 10 lockB.release() 11 lockA.release() 12 13 def doB(self): 14 lockB.acquire() 15 print(self.name,"gotlockB",time.ctime()) 16 time.sleep(2) 17 lockA.acquire() 18 print(self.name,"gotlockA",time.ctime()) 19 lockA.release() 20 lockB.release() 21 def run(self): 22 self.doA() 23 self.doB() 24 if __name__=="__main__": 25 26 lockA=threading.Lock() 27 lockB=threading.Lock() 28 threads=[] 29 for i in range(5): 30 threads.append(myThread()) 31 for t in threads: 32 t.start() 33 for t in threads: 34 t.join()
解决办法:使用递归锁
即重新定义一把锁:lock = threading.RLock() --> 递归锁
将所有的锁替换为递归锁即可。递归锁可以重复加锁。
RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
3.5 信号量(Semaphore)--> 相当于一把锁
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
1 import threading,time 2 class myThread(threading.Thread): 3 def run(self): 4 if semaphore.acquire(): 5 print(self.name) 6 time.sleep(5) 7 semaphore.release() 8 if __name__=="__main__": 9 semaphore=threading.Semaphore(5) 10 thrs=[] 11 for i in range(100): 12 thrs.append(myThread()) 13 for t in thrs: 14 t.start()
3.6 条件变量同步(Condition)--> 锁
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传入锁,对象自动创建一个RLock()。
1 wait():条件不满足时调用,线程会释放锁并进入等待阻塞; 2 notify():条件创造后调用,通知等待池激活一个线程; 3 notifyAll():条件创造后调用,通知等待池激活所有线程。
1 import threading,time 2 from random import randint 3 class Producer(threading.Thread): 4 def run(self): 5 global L 6 while True: 7 val=randint(0,100) 8 print('生产者',self.name,":Append"+str(val),L) 9 if lock_con.acquire(): 10 L.append(val) 11 lock_con.notify() 12 lock_con.release() 13 time.sleep(3) 14 class Consumer(threading.Thread): 15 def run(self): 16 global L 17 while True: 18 lock_con.acquire() 19 if len(L)==0: 20 lock_con.wait() 21 print('消费者',self.name,":Delete"+str(L[0]),L) 22 del L[0] 23 lock_con.release() 24 time.sleep(0.25) 25 26 if __name__=="__main__": 27 28 L=[] 29 lock_con=threading.Condition() 30 threads=[] 31 for i in range(5): 32 threads.append(Producer()) 33 threads.append(Consumer()) 34 for t in threads: 35 t.start() 36 for t in threads: 37 t.join()
3.7 同步条件(Event)
条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值 为False;
1 event.isSet():返回event的状态值; 2 3 event.wait():如果 event.isSet()==False将阻塞线程; 4 5 event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; 6 7 event.clear():恢复event的状态值为False。
示例:
1 import threading,time 2 class Boss(threading.Thread): 3 def run(self): 4 print("BOSS:今晚大家都要加班到22:00。") 5 event.isSet() or event.set() 6 time.sleep(5) 7 print("BOSS:<22:00>可以下班了。") 8 event.isSet() or event.set() 9 class Worker(threading.Thread): 10 def run(self): 11 event.wait() 12 print("Worker:哎……命苦啊!") 13 time.sleep(0.25) 14 event.clear() 15 event.wait() 16 print("Worker:OhYeah!") 17 if __name__=="__main__": 18 event=threading.Event() 19 threads=[] 20 for i in range(5): 21 threads.append(Worker()) 22 threads.append(Boss()) 23 for t in threads: 24 t.start() 25 for t in threads: 26 t.join()
3.8 队列(queue)-->多线程利器
queue中的方法:
创建一个“队列”对象 import Queue q = Queue.Queue(maxsize = 10) Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。 将一个值放入队列中 q.put(10) 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。 将一个值从队列中取出 q.get() 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。 Python Queue模块有三种队列及构造函数: 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize) 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize) 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 此包中的常用方法(q = Queue.Queue()): q.qsize() 返回队列的大小 q.empty() 如果队列为空,返回True,反之False q.full() 如果队列满了,返回True,反之False q.full 与 maxsize 大小对应 q.get([block[, timeout]]) 获取队列,timeout等待时间 q.get_nowait() 相当q.get(False) 非阻塞 q.put(item) 写入队列,timeout等待时间 q.put_nowait(item) 相当q.put(item, False) q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 q.join() 实际上意味着等到队列为空,再执行别的操作
示例:
1 import threading,queue 2 from time import sleep 3 from random import randint 4 class Production(threading.Thread): 5 def run(self): 6 while True: 7 r=randint(0,100) 8 q.put(r) 9 print("生产出来%s号包子"%r) 10 sleep(1) 11 class Proces(threading.Thread): 12 def run(self): 13 while True: 14 re=q.get() 15 print("吃掉%s号包子"%re) 16 if __name__=="__main__": 17 q=queue.Queue(10) 18 threads=[Production(),Production(),Production(),Proces()] 19 for t in threads: 20 t.start()