多线程
1.概念
1.1 进程:(有时也称为重量级进程),是一个执行中的程序。每个程序都有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。
1.2 线程:线程是操作系统够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1.3全局解释器锁: 尽管Python解释器中可以运行多个线程,但是在任意给定的时刻,只有一个线程会被解释器执行。这就是由全局解释器锁控制的(GIL)。
多线程的环境中,Python虚拟机会按照如下的步骤执行(借鉴与Python核心编程):
a.设置GIL
b.切换一个线程进去
c.执行如下操作之一:(1).指定数量的得字节码指令。(2).线程主动让出控制权
d.把线程设置睡眠状态(切换出线程)
e.解锁GIL
f.重复上述步骤
自我理解:任意时刻,一个进程中只会有一个线程被Python解释器调用,并且随机,排队中的线程获得下次GIL也是随机的。这样看来Python中的多线程是否多余了呢?其实不然,相对于计算密集型的线程可能不太能感受到多线程的效果,但IO密集型的线程就会有很好的体验。在Python中。这个可以用多进程,或者协程进行弥补,但是多进程又要考虑到进程之间的数据交流问题,下面谈!!!!
2.多线程(threading模块)
2.1 Thread 的属性和方法
Atttribute:
name ;线程名
ident: 线程的标识符
daemon: 布尔标志,表示这个线程是否为守护线程
Method:
start() 开始执行线程
run() 定义线程功能的方法(通常在子类中应用被开发者重写)
join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout,否则会一直阻塞
getName() 返回线程名
setName(name) 设定线程名
setDaemon() 把线程的守护标志设定为布尔值TRUE (必须在线程开始start()之前调用)
2.2.两种创建方式
a.创建Thread实例,传给他一个函数
import threading import time def func(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.func(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.func(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
b.派生Thread的子类,并创建子类的实例
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
2.3 join() 与 setDaemon()
setDaemon() :将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
3.同步锁(Lock)
问题:多个线程都在操作一个共享资源,造成了资源破坏(也就是,当一个线程的数据为未处理结束,其他线程已经开始)
不用join的原因:join会把整个线程给停住,失去了多线程的意义
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 # num-=1 lock.acquire() temp=num print('--get num:',num ) #time.sleep(0.1) num =temp-1 #对此公共变量进行-1操作 lock.release() num = 100 #设定一个共享变量 thread_list = [] lock=threading.Lock()#通过创建Lock对象获得锁 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num )
4.线程死锁和递归锁
死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
def dotA(): lockA.acquire() lockB.acquire() ''' 数据操作 ''' lockB.realse() lockA.realse() def dotB(): lockB.acquire() lockA.acquire() ''' 数据操作 ''' lockA.realse() lockB.realse() 此时lockA与lockB会互相的等待对方,造成死锁
解决方式:使用递归锁
为了支持在同一线程多次请求同一资源,Python提供了"可重入锁",threading.RLock,Rlock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire(),直到一个线程的所有的acquire都被relase,其他线程才能获得资源;将lock全部用threading.Rlock
5.条件同步变量
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞; notify():条件创造后调用,通知等待池激活一个线程; notifyAll():条件创造后调用,通知等待池激活所有线程。
import threading,time from random import randint class Producer(threading.Thread): def run(self): global L while True: val=randint(0,100) print('生产者',self.name,":Append"+str(val),L) if lock_con.acquire(): L.append(val) lock_con.notify() lock_con.release() time.sleep(3) class Consumer(threading.Thread): def run(self): global L while True: lock_con.acquire() if len(L)==0: lock_con.wait() print('消费者',self.name,":Delete"+str(L[0]),L) del L[0] lock_con.release() time.sleep(0.25) if __name__=="__main__": L=[] lock_con=threading.Condition() threads=[] for i in range(5): threads.append(Producer()) threads.append(Consumer()) for t in threads: t.start() for t in threads: t.join()
6.同步条件:
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
from threading import Event from threading import Thread import time def boss(event): print('今天晚上加班!!!') event.isSet() or event.set() time.sleep(5) print("下班了!!!") event.isSet() or event.set() def worker(event): event.wait() print("命苦呀!!!!") event.clear() event.wait() print('欧耶!!!!') def main(): event = Event() event.clear() threads = [] for i in range(4): threads.append(Thread(target=worker,args=(event,))) threads.append(Thread(target=boss,args=(event,))) for thread in threads: thread.start() for thread in threads: thread.join() print('all done') if __name__ == '__main__': main()
7.队列(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() 实际上意味着等到队列为空,再执行别的操作