多线程(threading模块)
线程与进程
Python自己没有线程和进程,Python中调用的操作系统的线程和进程。
1. 操作系统帮助开发者操作硬件。
2. 程序员写好代码在操作系统上运行(依赖解释器)。
"""
- 你写好代码
- 交给解释器运行: python s1.py
- 解释器读取代码,再交给操作系统去执行,根据你的代码去选择创建多少个线程/进程去执行(单进程/单线程)。
- 操作系统调用硬件:硬盘、cpu、网卡....
"""
1. 应用程序/进程/线程的关系? *****(面试题:进程/线程/协程的区别?)
进程和线程的区别:
第一:
进程是cpu资源分配的最小单元。
线程是cpu计算的最小单元。
第二:
一个进程中可以有多个线程。
第三:
对于Python来说他的进程和线程和其他语言有差异,是有GIL锁。
GIL锁保证一个进程中同一时刻只有一个线程被cpu调度。
2. 为什么要创建线程?
由于线程是cpu工作的最小单元,创建线程可以利用多核优势实现并行操作(Java/C#)。
注意:线程是为了工作。
3. 为什么要创建进程?
进程和进程之间做数据隔离(Java/C#)。
注意:进程是为了提供环境让线程工作。
4 线程创建的越多越好吗?不好
线程之间进行切换时,要做上下文管理
5 线程安全,多线程操作时,内部会让所有线程排队处理。如:list/dict/Queue
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import threading 2 3 v = [] 4 def func(arg): 5 v.append(arg) # 线程安全 6 print(v) 7 for i in range(10): 8 t =threading.Thread(target=func,args=(i,)) 9 t.start()
为什么要加锁?
- 非线程安全
- 控制一段代码
什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
A thread is an execution context, which is all the information a CPU needs to execute a stream of instructions.
Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped. One way to achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.
If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back, and resume it from where you were.
Threads work in the same way. A CPU is giving you the illusion that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.
On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers.
Last: threads are different from processes. A thread is a context of execution, while a process is a bunch of resources associated with a computation. A process can have one or many threads.
Clarification: the resources associated with a process include memory pages (all the threads in a process have the same view of the memory), file descriptors (e.g., open sockets), and security credentials (e.g., the ID of the user who started the process).
什么是进程(process)?
An executing instance of a program is called a process.
Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.
进程与线程的区别?
- Threads share the address space of the process that created it; processes have their own address space.
- Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
- Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
- New threads are easily created; new processes require duplication of the parent process.
- Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
- Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
Python GIL(Global Interpreter Lock)
CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.
在CPython中,全局解释器锁(global interpreter lock, GIL)是一个互斥体,它防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它强制执行的保证。)
GIL锁,全局解释器锁。用于限制一个进程中同一时刻只有一个线程被cpu调度。
扩展:默认GIL锁在执行100个cpu指令(过期时间)。
查看gil切换的指令数的方法:
import sys
v1 = sys.getcheckinterval()
print(v1) #100
Python多线程情况下:
- 计算密集型操作:效率低。(GIL锁)
- IO操作: 效率高
Python多进程的情况下:
- 计算密集型操作:效率高(浪费资源)。 不得已而为之。
- IO操作: 效率高 (浪费资源)。
以后写Python时:
IO密集型用多线程: 文件/输入输出/socket网络通信
计算密集型用多进程。
扩展:
Java多线程情况下:
- 计算密集型操作:效率高。
- IO操作: 效率高
Python多进程的情况下:
- 计算密集型操作:效率高(浪费资源)。
- IO操作: 效率高 浪费资源)。
threading模块
一 线程的2种调用方式
直接调用
实例1:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
继承式调用:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
二 Join & Daemon
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import threading 2 from time import ctime,sleep 3 import time 4 5 def music(func): 6 for i in range(2): 7 print ("Begin listening to %s. %s" %(func,ctime())) 8 sleep(4) 9 print("end listening %s"%ctime()) 10 11 def move(func): 12 for i in range(2): 13 print ("Begin watching at the %s! %s" %(func,ctime())) 14 sleep(5) 15 print('end watching %s'%ctime()) 16 17 threads = [] 18 t1 = threading.Thread(target=music,args=('七里香',)) 19 threads.append(t1) 20 t2 = threading.Thread(target=move,args=('阿甘正传',)) 21 threads.append(t2) 22 23 if __name__ == '__main__': 24 25 for t in threads: 26 # t.setDaemon(True) 27 t.start() 28 # t.join() 29 # t1.join() 30 t2.join()########考虑这三种join位置下的结果? 31 print ("all over %s" %ctime())
setDaemon(True):
将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦
join():
在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
# 无参数,让主线程在这里等着,等到子线程t1执行完毕,才可以继续往下走。
# 有参数,让主线程在这里最多等待n秒,无论是否执行完毕,会继续往下走。
其它方法
# start 是开始运行线程吗?不是
# start 告诉cpu,我已经准备就绪,你可以调度我了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 thread 模块提供的其他方法: 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(): 设置线程名。
三 同步锁(Lock) (一次放一个)
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 # num-=1 temp=num print('--get num:',num ) #time.sleep(0.1) num =temp-1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] 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 )
注意:
1: why num-=1没问题呢?这是因为动作太快(完成这个动作在切换的时间内)
2: if sleep(1),现象会更明显,100个线程每一个一定都没有执行完就进行了切换,我们说过sleep就等效于IO阻塞,1s之内不会再切换回来,所以最后的结果一定是99.
多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?
有同学会想用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() 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 )
问题解决,但
请问:同步锁与GIL的关系?
GIL本质是一把互斥锁,但GIL锁住的是解释器级别的数据
同步锁,锁的是解释器以外的共享资源,例如:硬盘上的文件 控制台,对于这种不属于解释器的数据资源就应该自己加锁处理
Python的线程在GIL的控制之下,线程之间,对整个python解释器,对python提供的C API的访问都是互斥的,这可以看作是Python内核级的互斥机制。但是这种互斥是我们不能控制的,我们还需要另外一种可控的互斥机制———用户级互斥。内核级通过互斥保护了内核的共享资源,同样,用户级互斥保护了用户程序中的共享资源。
但是如果你有个操作比如 x += 1,这个操作需要多个bytecodes操作,在执行这个操作的多条bytecodes期间的时候可能中途就换thread了,这样就出现了data races的情况了。
四 线程死锁和递归锁(1次放一个)
注:递归锁RLock最常用, Lock、BoundedSemaphore、Condition、Event不常用
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()#等待线程结束,后面再讲。
注:为啥锁的内部还可能加锁(因为内部加锁的部分可能被其他地方调用,如函数等 被别处调用可能会使数据改变而影响到正在进行的线程)
解决办法:使用递归锁,将
1
2
|
lockA = threading.Lock() lockB = threading.Lock()<br> #--------------<br>lock=threading.RLock() |
为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
应用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time 2 3 import threading 4 5 class Account: 6 def __init__(self, _id, balance): 7 self.id = _id 8 self.balance = balance 9 self.lock = threading.RLock() 10 11 def withdraw(self, amount): 12 13 with self.lock: 14 self.balance -= amount 15 16 def deposit(self, amount): 17 with self.lock: 18 self.balance += amount 19 20 21 def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景 22 23 with self.lock: 24 interest=0.05 25 count=amount+amount*interest 26 27 self.withdraw(count) 28 29 30 def transfer(_from, to, amount): 31 32 #锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的 33 _from.withdraw(amount) 34 35 to.deposit(amount) 36 37 38 39 alex = Account('alex',1000) 40 yuan = Account('yuan',1000) 41 42 t1=threading.Thread(target = transfer, args = (alex,yuan, 100)) 43 t1.start() 44 45 t2=threading.Thread(target = transfer, args = (yuan,alex, 200)) 46 t2.start() 47 48 t1.join() 49 t2.join() 50 51 print('>>>',alex.balance) 52 print('>>>',yuan.balance)
五 条件变量同步(Condition)(一次放n个)
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传人锁,对象自动创建一个RLock()。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞; notify():条件创造后调用,通知等待池激活一个线程;可加参数,激活(参数)个 notifyAll():条件创造后调用,通知等待池激活所有线程。
实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time 2 import threading 3 4 lock = threading.Condition() 5 6 # ############## 方式一 ############## 7 8 def func(arg): 9 print('线程进来了') 10 lock.acquire() 11 lock.wait() # 加锁 12 13 print(arg) 14 time.sleep(1) 15 16 lock.release() 17 18 19 for i in range(10): 20 t =threading.Thread(target=func,args=(i,)) 21 t.start() 22 23 while True: 24 inp = int(input('>>>')) 25 26 lock.acquire() 27 lock.notify(inp) 28 lock.release() 29 30 31 # ############## 方式二 ############## 32 """ 33 def xxxx(): 34 print('来执行函数了') 35 input(">>>") 36 # ct = threading.current_thread() # 获取当前线程 37 # ct.getName() 38 return True 39 40 def func(arg): 41 print('线程进来了') 42 lock.wait_for(xxxx) 43 print(arg) 44 time.sleep(1) 45 46 for i in range(10): 47 t =threading.Thread(target=func,args=(i,)) 48 t.start() 49 50 """
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
六 同步条件(Event)(一次放所有)
条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值 为False;
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
实例1:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
实例2:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import threading,time 2 import random 3 def light(): 4 if not event.isSet(): 5 event.set() #wait就不阻塞 #绿灯状态 6 count = 0 7 while True: 8 if count < 10: 9 print('\033[42;1m--green light on---\033[0m') 10 elif count <13: 11 print('\033[43;1m--yellow light on---\033[0m') 12 elif count <20: 13 if event.isSet(): 14 event.clear() 15 print('\033[41;1m--red light on---\033[0m') 16 else: 17 count = 0 18 event.set() #打开绿灯 19 time.sleep(1) 20 count +=1 21 def car(n): 22 while 1: 23 time.sleep(random.randrange(10)) 24 if event.isSet(): #绿灯 25 print("car [%s] is running.." % n) 26 else: 27 print("car [%s] is waiting for the red light.." %n) 28 if __name__ == '__main__': 29 event = threading.Event() 30 Light = threading.Thread(target=light) 31 Light.start() 32 for i in range(3): 33 t = threading.Thread(target=car,args=(i,)) 34 t.start()
示例3:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time 2 import threading 3 4 lock = threading.Event() 5 6 7 def func(arg): 8 print('线程来了') 9 lock.wait() # 加锁:红灯 10 print(arg) 11 12 13 for i in range(10): 14 t =threading.Thread(target=func,args=(i,)) 15 t.start() 16 17 input(">>>>") 18 lock.set() # 绿灯 19 20 21 lock.clear() # 再次变红灯 22 23 for i in range(10): 24 t =threading.Thread(target=func,args=(i,)) 25 t.start() 26 27 input(">>>>") 28 lock.set()
七 信号量(Semaphore)(一次放n个)
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
实例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
八 多线程利器(queue)
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()
实例2:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time,random 2 import queue,threading 3 q = queue.Queue() 4 def Producer(name): 5 count = 0 6 while count <20: 7 time.sleep(random.randrange(3)) 8 q.put(count) 9 print('Producer %s has produced %s baozi..' %(name, count)) 10 count +=1 11 def Consumer(name): 12 count = 0 13 while count <20: 14 time.sleep(random.randrange(4)) 15 if not q.empty(): 16 data = q.get() 17 print(data) 18 print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) 19 else: 20 print("-----no baozi anymore----") 21 count +=1 22 p1 = threading.Thread(target=Producer, args=('A',)) 23 c1 = threading.Thread(target=Consumer, args=('B',)) 24 p1.start() 25 c1.start()
实例3:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #实现一个线程不断生成一个随机数到一个队列中(考虑使用Queue这个模块) 2 # 实现一个线程从上面的队列里面不断的取出奇数 3 # 实现另外一个线程从上面的队列里面不断取出偶数 4 5 import random,threading,time 6 from queue import Queue 7 #Producer thread 8 class Producer(threading.Thread): 9 def __init__(self, t_name, queue): 10 threading.Thread.__init__(self,name=t_name) 11 self.data=queue 12 def run(self): 13 for i in range(10): #随机产生10个数字 ,可以修改为任意大小 14 randomnum=random.randint(1,99) 15 print ("%s: %s is producing %d to the queue!" % (time.ctime(), self.getName(), randomnum)) 16 self.data.put(randomnum) #将数据依次存入队列 17 time.sleep(1) 18 print ("%s: %s finished!" %(time.ctime(), self.getName())) 19 20 #Consumer thread 21 class Consumer_even(threading.Thread): 22 def __init__(self,t_name,queue): 23 threading.Thread.__init__(self,name=t_name) 24 self.data=queue 25 def run(self): 26 while 1: 27 try: 28 val_even = self.data.get(1,5) #get(self, block=True, timeout=None) ,1就是阻塞等待,5是超时5秒 29 if val_even%2==0: 30 print ("%s: %s is consuming. %d in the queue is consumed!" % (time.ctime(),self.getName(),val_even)) 31 time.sleep(2) 32 else: 33 self.data.put(val_even) 34 time.sleep(2) 35 except: #等待输入,超过5秒 就报异常 36 print ("%s: %s finished!" %(time.ctime(),self.getName())) 37 break 38 class Consumer_odd(threading.Thread): 39 def __init__(self,t_name,queue): 40 threading.Thread.__init__(self, name=t_name) 41 self.data=queue 42 def run(self): 43 while 1: 44 try: 45 val_odd = self.data.get(1,5) 46 if val_odd%2!=0: 47 print ("%s: %s is consuming. %d in the queue is consumed!" % (time.ctime(), self.getName(), val_odd)) 48 time.sleep(2) 49 else: 50 self.data.put(val_odd) 51 time.sleep(2) 52 except: 53 print ("%s: %s finished!" % (time.ctime(), self.getName())) 54 break 55 #Main thread 56 def main(): 57 queue = Queue() 58 producer = Producer('Pro.', queue) 59 consumer_even = Consumer_even('Con_even.', queue) 60 consumer_odd = Consumer_odd('Con_odd.',queue) 61 producer.start() 62 consumer_even.start() 63 consumer_odd.start() 64 producer.join() 65 consumer_even.join() 66 consumer_odd.join() 67 print ('All threads terminate!') 68 69 if __name__ == '__main__': 70 main()
注意:列表是线程不安全的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import threading,time 2 3 li=[1,2,3,4,5] 4 5 def pri(): 6 while li: 7 a=li[-1] 8 print(a) 9 time.sleep(1) 10 try: 11 li.remove(a) 12 except: 13 print('----',a) 14 15 t1=threading.Thread(target=pri,args=()) 16 t1.start() 17 t2=threading.Thread(target=pri,args=()) 18 t2.start()
九 Python中的上下文管理器(contextlib模块)
上下文管理器的任务是:代码块执行前准备,代码块执行后收拾
1、如何使用上下文管理器:
如何打开一个文件,并写入"hello world"
1
2
3
4
5
|
filename = "my.txt" mode = "w" f = open (filename,mode) f.write( "hello world" ) f.close() |
当发生异常时(如磁盘写满),就没有机会执行第5行。当然,我们可以采用try-finally语句块进行包装:
1
2
3
4
5
|
writer = open (filename,mode) try : writer.write( "hello world" ) finally : writer.close() |
当我们进行复杂的操作时,try-finally语句就会变得丑陋,采用with语句重写:
1
2
|
with open (filename,mode) as writer: writer.write( "hello world" ) |
as指代了从open()函数返回的内容,并把它赋给了新值。with完成了try-finally的任务。
2、自定义上下文管理器
with语句的作用类似于try-finally,提供一种上下文机制。要应用with语句的类,其内部必须提供两个内置函数__enter__和__exit__。前者在主体代码执行前执行,后者在主体代码执行后执行。as后面的变量,是在__enter__函数中返回的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class echo(): def output( self ): print "hello world" def __enter__( self ): print "enter" return self #可以返回任何希望返回的东西 def __exit__( self ,exception_type,value,trackback): print "exit" if exception_type = = ValueError: return True else : return Flase >>>with echo as e: e.output() 输出: enter hello world exit |
完备的__exit__函数如下:
1
|
def __exit__( self ,exc_type,exc_value,exc_tb) |
其中,exc_type:异常类型;exc_value:异常值;exc_tb:异常追踪信息
当__exit__返回True时,异常不传播
3、contextlib模块
contextlib模块的作用是提供更易用的上下文管理器,它是通过Generator实现的。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制,常用框架如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from contextlib import contextmanager @contextmanager def make_context(): print 'enter' try : yield "ok" except RuntimeError,err: print 'error' ,err finally : print 'exit' >>>with make_context() as value: print value 输出为: enter ok exit |
其中,yield写入try-finally中是为了保证异常安全(能处理异常)as后的变量的值是由yield返回。yield前面的语句可看作代码块执行前操作,yield之后的操作可以看作在__exit__函数中的操作。
以线程锁为例:
@contextlib.contextmanager def loudLock(): print 'Locking' lock.acquire() yield print 'Releasing' lock.release() with loudLock(): print 'Lock is locked: %s' % lock.locked() print 'Doing something that needs locking' #Output: #Locking #Lock is locked: True #Doing something that needs locking #Releasing
4、contextlib.nested:减少嵌套
对于:
1
2
3
|
with open (filename,mode) as reader: with open (filename1,mode1) as writer: writer.write(reader.read()) |
可以通过contextlib.nested进行简化:
1
2
|
with contextlib.nested( open (filename,mode), open (filename1,mode1)) as (reader,writer): writer.write(reader.read()) |
在python 2.7及以后,被一种新的语法取代:
1
2
|
with open (filename,mode) as reader, open (filename1,mode1) as writer: writer.write(reader.read()) |
5、contextlib.closing()
file类直接支持上下文管理器API,但有些表示打开句柄的对象并不支持,如urllib.urlopen()返回的对象。还有些遗留类,使用close()方法而不支持上下文管理器API。为了确保关闭句柄,需要使用closing()为它创建一个上下文管理器(调用类的close方法)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import contextlib 2 class myclass(): 3 def __init__(self): 4 print '__init__' 5 def close(self): 6 print 'close()' 7 8 with contextlib.closing(myclass()): 9 print 'ok' 10 11 输出: 12 __init__ 13 ok 14 close()
十 线程池
线程池详见:https://www.cnblogs.com/hoojjack/p/10846010.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from concurrent.futures import ThreadPoolExecutor 2 import time 3 4 def task(a1,a2): 5 time.sleep(2) 6 print(a1,a2) 7 8 # 创建了一个线程池(这里最多5个线程) 9 pool = ThreadPoolExecutor(5) 10 11 for i in range(40): 12 # 去线程池中申请一个线程,让线程执行task函数。 13 pool.submit(task,i,8)
1 # # ######### 线程 ################ 2 # import time 3 # import threading 4 # 5 # def task(arg): 6 # time.sleep(50) 7 # 8 # while True: 9 # num = input('>>>') 10 # t = threading.Thread(target=task,args=(num,)) 11 # t.start() 12 13 # ############# 线程池 ############# 14 # import time 15 # from concurrent.futures import ThreadPoolExecutor 16 # 17 # def task(arg): 18 # time.sleep(50) 19 # 20 # pool = ThreadPoolExecutor(20) 21 # while True: 22 # num = input('>>>') 23 # pool.submit(task,num)
注:一定要用线程池,防止用户无节制开线程
自定义线程如下:
简单版本:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import queue 2 import threading 3 import time 4 5 class ThreadPool(object): 6 7 def __init__(self, max_num=20): 8 self.queue = queue.Queue(max_num) 9 for i in range(max_num): 10 self.queue.put(threading.Thread) 11 12 def get_thread(self): 13 return self.queue.get() 14 15 def add_thread(self): 16 self.queue.put(threading.Thread) 17 18 19 ''' 20 pool = ThreadPool(10) 21 22 def func(arg, p): 23 print(arg) 24 time.sleep(1) 25 p.add_thread() 26 27 28 for i in range(30): 29 Pool = pool.get_thread() 30 t = Pool(target=func, args=(i, pool)) 31 t.start() 32 '''
复杂版本:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import queue 5 import threading 6 import contextlib 7 import time 8 9 StopEvent = object() 10 11 class ThreadPool(object): 12 13 def __init__(self, max_num, max_task_num = None): 14 if max_task_num: 15 self.q = queue.Queue(max_task_num) 16 else: 17 self.q = queue.Queue() 18 self.max_num = max_num 19 self.cancel = False 20 self.terminal = False 21 self.generate_list = [] 22 self.free_list = [] 23 24 def run(self, func, args, callback=None): 25 """ 26 线程池执行一个任务 27 :param func: 任务函数 28 :param args: 任务函数所需参数 29 :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) 30 :return: 如果线程池已经终止,则返回True否则None 31 """ 32 if self.cancel: 33 return 34 if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: 35 self.generate_thread() 36 w = (func, args, callback,)#主线程 37 self.q.put(w)#主线程 38 39 def generate_thread(self): 40 """ 41 创建一个线程 42 """ 43 t = threading.Thread(target=self.call) 44 t.start() 45 46 def call(self): 47 """ 48 循环去获取任务函数并执行任务函数 49 """ 50 current_thread = threading.currentThread() 51 self.generate_list.append(current_thread) 52 53 event = self.q.get()#if q为空,则阻塞住,一直等到有任务进来并把它取出来 54 while event != StopEvent: 55 56 func, arguments, callback = event 57 try: 58 result = func(*arguments) 59 success = True 60 except Exception as e: 61 success = False 62 result = None 63 64 if callback is not None: 65 try: 66 callback(success, result) 67 except Exception as e: 68 pass 69 70 with self.worker_state(self.free_list, current_thread): 71 if self.terminal: 72 event = StopEvent 73 else: 74 event = self.q.get()#key:该线程在这里继续等待新的任务,任务来了,继续执行 75 #暂时将该线程对象放到free_list中。 76 else: 77 78 self.generate_list.remove(current_thread) 79 80 def close(self): 81 """ 82 执行完所有的任务后,所有线程停止 83 """ 84 self.cancel = True 85 full_size = len(self.generate_list) 86 while full_size: 87 self.q.put(StopEvent) 88 full_size -= 1 89 90 def terminate(self): 91 """ 92 无论是否还有任务,终止线程 93 """ 94 self.terminal = True 95 96 while self.generate_list: 97 self.q.put(StopEvent) 98 99 self.q.queue.clear() 100 101 @contextlib.contextmanager 102 def worker_state(self, free_list, worker_thread): 103 """ 104 用于记录线程中正在等待的线程数 105 """ 106 free_list.append(worker_thread)#新的任务来的时候判断 107 # if len(self.free_list) == 0 and len(self.generate_list) < self.max_num 108 # 任务得创建新的线程来处理;如果len(self.free_list) != 0:由阻塞着的存在free_list中的线程处理(event = self.q.get()) 109 try: 110 yield 111 finally: 112 free_list.remove(worker_thread) 113 114 # How to use 115 116 117 pool = ThreadPool(5) 118 119 def callback(status, result): 120 # status, execute action status 121 # result, execute action return value 122 pass 123 124 125 def action(i): 126 time.sleep(1) 127 print(i) 128 129 for i in range(30): 130 ret = pool.run(action, (i,), callback) 131 132 time.sleep(2) 133 print(len(pool.generate_list), len(pool.free_list)) 134 print(len(pool.generate_list), len(pool.free_list)) 135 136 # pool.close() 137 # pool.terminate()
延伸:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import contextlib 2 import socket 3 @contextlib.contextmanager 4 def context_socket(host,port): 5 sk=socket.socket() 6 sk.bind((host,port)) 7 sk.listen(5) 8 try: 9 yield sk 10 finally:sk.close() 11 12 with context_socket('127.0.0.1',8888) as socket: 13 print(socket)
作用:
内部自动为每个线程维护一个空间(字典),用于当前存取属于自己的值。保证线程之间的数据隔离。
{
线程ID: {...}
线程ID: {...}
线程ID: {...}
线程ID: {...}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import time 2 import threading 3 4 v = threading.local() 5 6 def func(arg): 7 # 内部会为当前线程创建一个空间用于存储:phone=自己的值 8 v.phone = arg 9 time.sleep(2) 10 print(v.phone,arg) # 去当前线程自己空间取值 11 12 for i in range(10): 13 t =threading.Thread(target=func,args=(i,)) 14 t.start()