python 线程
一颗cpu同一时刻只能有一个线程执行。在cpython解释器中,同一时刻只能让同一个进程中只有一个线程进入解释器
GIL是内核级别的锁:为了保护内核,被多线程干扰
线程锁是用户级别的锁:为了保护程序里面的数据
进程与线程
线程:
是最小的执行单位,是一堆指令的集合。cpu会记录上下文关系
数据之间是共享的
一个进程至少有一个线程
进程:
进程是不修改数据的。
一堆资源(线程、cpu、内存等)的管理的集合
进程之间的数据是不共享的。
GIL:
在cpython解释器中,同一时刻只能让同一个进程中只有一个线程进入解释器。因为同一进程中的线程是共享数据的
在开发这么语言的时候,只有一颗cpu,为了保护在多线程保护数据一致性,所以开发了GIL。去锁有大咖尝试做过,但是效率更低
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once.
This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists,
other features have grown to depend on the guarantees that it enforces.)
计算密集型与I/O密集型
io密集型:
线程在遇到I/0(sock的收发,文件的读写,sleep等)的时候,会自动进行切换到另外一个线程计算。就算只有一颗cpu的情况下,也大量节省了时间。
如下代码,多线程会比串行节约一倍的时间
import time import threading def foo1(): time.sleep(8) print("1") def foo2(): time.sleep(8) print("2") start=time.time() # foo1() # foo2() a=threading.Thread(target=foo1) b=threading.Thread(target=foo2) a.start() b.start() a.join() b.join() end=time.time() print("run time:",end-start)
计算密集型:
下列代码,就是io密集型的表现,使用串行算法与 多线程算法 两者的时间几乎没区别(在python2.7和3.4中,多线程io因为不断切换线程,时间比串行慢了一半。3.5已经优化) python中因为GIL的存在,线程只能在一个cpu中计算。对应计算密集型多线程 不适用
import time import threading def add(n): sum = 0 for i in range(n): sum += i print(sum) start = time.time() # add(100000000) # add(200000000) a=threading.Thread(target=add,args=(100000000,)) b=threading.Thread(target=add,args=(200000000,)) a.start() b.start() a.join() b.join() end = time.time() print("time:",start-end)
解决同一个进程中的多线程在同一时刻只能有一个进入cpu的问题,可以利用多协程与携程 等等解决方法。
线程的另外一种调用方式:
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()
jion方法:
阻塞线程,等待线程执行完毕。 线程可以通过列表装载。
setdeamon:
守护进程,主进程死了,下面的线程全挂、
线程锁
是为了解决线程之间 操作共享数据的问题
线程不安全:
如下代码:线程之间共享数据,假如第一次一个线程拿到全局变量,进行计算的时候,到了cpu的执行时间,不会进行最后一步重新赋值全局变量。
但是第二次线程拿到全局变量,执行完毕,赋值给全局变量。两次的执行效果都是一样。
import threading,time def add(): global num temp = num temp -= 1 time.sleep(0.001) num = temp num = 200 thread_list=[] for i in range(100): t = threading.Thread(target=add,args=()) t.start() thread_list.append(t) for t in thread_list: t.join() print(num)
解决方案:线程锁,就是加在线程上,告诉解释器,我必须执行完,你才能给我进行切换!
acquire 与 release 之间变成了串行,就算遇到I/0也会被锁住
但是在该线程 锁之外的东西(如下print(ok))还是可以进行cpu切换的,
这就是和join的区别。 join是把线程所有的部分都变成了串行
import threading,time def add(): global num #!!acquire 与 release 之间变成了串行,就算遇到I/0也会被锁住 # 但是在该线程 锁之外的东西(如下print(ok))还是可以进行cpu切换的, # 这就是和join的区别。 join是把线程所有的部分都变成了串行 print("ok") r.acquire() temp = num temp -= 1 time.sleep(0.001) num = temp r.release() num = 200 thread_list=[] r = threading.Lock() for i in range(100): t = threading.Thread(target=add,args=()) t.start() thread_list.append(t) for t in thread_list: t.join() print(num)
GIL 是 一个进程的所有线程,同一时刻只能一个进入cpu
线程锁 是保证 一个进程中,多线程操作共享数据的时候,为了避免到时 切换cpu而操作数据的不安全。
递归锁:普通的锁 只能被使用一次, 相互等待锁的时候,就会被锁死。(如下代码)递归锁 维护了 一个 计数器与对应列表。
import threading,time class myThread(threading.Thread): def doA(self): lockA.acquire() print(self.name,"gotlockA",time.ctime()) time.sleep(3) lockB.acquire() print(self.name,"gotlockB",time.ctime()) lockB.release() lockA.release() def doB(self): lockB.acquire() print(self.name,"gotlockB",time.ctime()) time.sleep(2) lockA.acquire() print(self.name,"gotlockA",time.ctime()) lockA.release() lockB.release() def run(self): self.doA() self.doB() if __name__=="__main__": lockA=threading.Lock() lockB=threading.Lock() threads=[] for i in range(5): threads.append(myThread()) for t in threads: t.start() for t in threads: t.join()#等待线程结束,后面再讲。
递归锁在类中包含 账户 转账、取现的时候,每个方法都加锁,同时一个方法调用前面函数,就需要用到递归锁了
信号量锁:
同一时刻只有三个链接并发。
r = threading.BoundedSemaphore(3)
r.acqure()
r.release()
条件同步锁
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,
它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传人锁,对象自动创建一个RLock()。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞;等到了notify通知时候,会从acquire从新执行
notify():条件创造后调用,通知等待池激活一个线程;
notifyAll():条件创造后调用,通知等待池激活所有线程。
enevt同步
这个不是锁了,但是效果类似条件同步锁
创建的时候,默认是Flase flag=thread.Event()
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程;当set的时候,立马执行 event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
import threading,time import random def light(): if not event.isSet(): event.set() #wait就不阻塞 #绿灯状态 count = 0 while True: if count < 10: print('\033[42;1m--green light on---\033[0m') elif count <13: print('\033[43;1m--yellow light on---\033[0m') elif count <20: if event.isSet(): event.clear() print('\033[41;1m--red light on---\033[0m') else: count = 0 event.set() #打开绿灯 time.sleep(1) count +=1 def car(n): while 1: time.sleep(random.randrange(10)) if event.isSet(): #绿灯 print("car [%s] is running.." % n) else: print("car [%s] is waiting for the red light.." %n) if __name__ == '__main__': event = threading.Event() Light = threading.Thread(target=light) Light.start() for i in range(3): t = threading.Thread(target=car,args=(i,)) t.start()
队列:今后常用的线程之间通信的利器
在单线程这个根本没啥用,在多线程中,使用列表 线程不安全(多个线程 取或者放会覆盖)。quqe自己默认有个锁,保证了线程的安全。
创建一个“队列”对象 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 from time import sleep from random import randint class Production(threading.Thread): def run(self): while True: r=randint(0,100) q.put(r) print("生产出来%s号包子"%r) sleep(1) class Proces(threading.Thread): def run(self): while True: re=q.get() print("吃掉%s号包子"%re) if __name__=="__main__": q=queue.Queue(10) threads=[Production(),Production(),Production(),Proces()] for t in threads: t.start()
实例2:
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 def Consumer(name): count = 0 while count <20: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('A',)) c1 = threading.Thread(target=Consumer, args=('B',)) p1.start() c1.start()
实例3:
#实现一个线程不断生成一个随机数到一个队列中(考虑使用Queue这个模块) # 实现一个线程从上面的队列里面不断的取出奇数 # 实现另外一个线程从上面的队列里面不断取出偶数 import random,threading,time from queue import Queue #Producer thread class Producer(threading.Thread): def __init__(self, t_name, queue): threading.Thread.__init__(self,name=t_name) self.data=queue def run(self): for i in range(10): #随机产生10个数字 ,可以修改为任意大小 randomnum=random.randint(1,99) print ("%s: %s is producing %d to the queue!" % (time.ctime(), self.getName(), randomnum)) self.data.put(randomnum) #将数据依次存入队列 time.sleep(1) print ("%s: %s finished!" %(time.ctime(), self.getName())) #Consumer thread class Consumer_even(threading.Thread): def __init__(self,t_name,queue): threading.Thread.__init__(self,name=t_name) self.data=queue def run(self): while 1: try: val_even = self.data.get(1,5) #get(self, block=True, timeout=None) ,1就是阻塞等待,5是超时5秒 if val_even%2==0: print ("%s: %s is consuming. %d in the queue is consumed!" % (time.ctime(),self.getName(),val_even)) time.sleep(2) else: self.data.put(val_even) time.sleep(2) except: #等待输入,超过5秒 就报异常 print ("%s: %s finished!" %(time.ctime(),self.getName())) break class Consumer_odd(threading.Thread): def __init__(self,t_name,queue): threading.Thread.__init__(self, name=t_name) self.data=queue def run(self): while 1: try: val_odd = self.data.get(1,5) if val_odd%2!=0: print ("%s: %s is consuming. %d in the queue is consumed!" % (time.ctime(), self.getName(), val_odd)) time.sleep(2) else: self.data.put(val_odd) time.sleep(2) except: print ("%s: %s finished!" % (time.ctime(), self.getName())) break #Main thread def main(): queue = Queue() producer = Producer('Pro.', queue) consumer_even = Consumer_even('Con_even.', queue) consumer_odd = Consumer_odd('Con_odd.',queue) producer.start() consumer_even.start() consumer_odd.start() producer.join() consumer_even.join() consumer_odd.join() print ('All threads terminate!') if __name__ == '__main__': main()
线程不安全实例:
import threading,time li=[1,2,3,4,5] def pri(): while li: a=li[-1] print(a) time.sleep(1) try: li.remove(a) except: print('----',a) t1=threading.Thread(target=pri,args=()) t1.start() t2=threading.Thread(target=pri,args=()) t2.start()