python学习笔记-线程进程协程
操作系统介绍
操作系统的历史
略
进程线程
进程的定义:
进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。
我们编写的程序用来描述进程要完成哪些功能以及如何完成; 数据集则是程序在执行过程中所需要使用的资源; 进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
线程的定义:
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
线程和进程的关系:
1 一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器) 2 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 3 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和 程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 4 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调 度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程
自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是
它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
python的GIL
GIL:全局解释锁
因为有GIL,所以,同一时刻,只有一个线程被cpu执行
无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
一、线程
1、调用方式
两种调用方式,直接调用和继承式调用,常用的就是直接调用
例子1,直接调用
import threading import time def Hi(num): print("hello %s"%num) time.sleep(3) if __name__=="__main__": t1=threading.Thread(target=Hi,args=(10,))#创建了一个线程对象 t1.start() t2 = threading.Thread(target=Hi, args=(9,)) t2.start() print("ending") #执行结果为:主线程print语句,t1,t2同时执行。主线程执行完成后等待新开的两个线程完成后才结束
例子2,继承式调用
import time import threading 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() print("ending....")
2、join和setDaemon方法
例子1,join方法
import threading import time def music(): print("begin to listen %s"%time.ctime()) time.sleep(3) print("stop to listen %s"%time.ctime()) def game(): print("begin to play %s"%time.ctime()) time.sleep(5) print("stop to play %s"%time.ctime()) if __name__=="__main__": t1=threading.Thread(target=music) t2=threading.Thread(target=game) t1.start() t2.start() t1.join() t2.join()#表示等待线程执行完成后再执行主线程 print("ending")
例子2,setDaemon方法
import threading import time def music(): print("begin to listen %s"%time.ctime()) time.sleep(3) print("stop to listen %s"%time.ctime()) def game(): print("begin to play %s"%time.ctime()) time.sleep(5) print("stop to play %s"%time.ctime()) if __name__=="__main__": t1=threading.Thread(target=music) t2=threading.Thread(target=game) #t1.setDaemon(True) #要在start之前设置。守护线程,等待主线程执行完成后就结束 t2.setDaemon(True) t1.start() t2.start() # print(t1.isAlive()) #True # print(t1.is_alive()) #True # print(t2.getName())#Thread-2 # print(threading.currentThread())#<_MainThread(MainThread, started 6496)> # print(threading.enumerate()) #[<_MainThread(MainThread, started 6496)>, <Thread(Thread-1, started 4444)>, <Thread(Thread-2, started daemon 10576)>] # print(threading.active_count())#3 # print(threading.activeCount()) #3 print("ending")
其他方法
#实例的方法
run():用以表示线程活动的方法 start():启动线程活动 isAlive():返回线程是否活动的 getName():返回线程名 setName():设置线程名 #threading模块提供的一些方法: threading.currenThread():返回当前线程变量 threading.enumerate():返回一个包含正在运行线程的list,不包括启动前和终止后的线程 threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())的结果相同
3、同步锁
例子1,场景
import threading import time def sub(): global num temp=num time.sleep(0.01) num=temp-1 num=100 l=[] for i in range(100): t=threading.Thread(target=sub) t.start() l.append(t) for t in l: t.join() print(num) #结果为95,83,96等不固定值,并不是想要的正确的值
例子2,Lock加锁解决
import threading import time def sub(): global num lock.acquire() #加锁 temp=num time.sleep(0.01) num=temp-1 lock.release() #释放 num=100 l=[] lock=threading.Lock() #创建锁 for i in range(100): t=threading.Thread(target=sub) t.start() l.append(t) for t in l: t.join() print(num)
4、递归锁
例子1,死锁的例子
import threading import time class MyThread(threading.Thread): def actionA(self): A.acquire() print(self.name,"gotA",time.ctime()) time.sleep(2) B.acquire() print(self.name,"gotB",time.ctime()) time.sleep(1) B.release() A.release() def actionB(self): B.acquire() print(self.name, "gotB", time.ctime()) time.sleep(2) A.acquire() print(self.name, "gotA", time.ctime()) time.sleep(1) A.release() B.release() def run(self): self.actionA() self.actionB() if __name__=="__main__": A=threading.Lock() B=threading.Lock() l=[] for i in range(5): t=MyThread() t.start() l.append(t)
结果为:
Thread-1 gotA Sun Apr 26 23:04:46 2020 Thread-1 gotB Sun Apr 26 23:04:48 2020 Thread-2 gotA Sun Apr 26 23:04:49 2020 Thread-1 gotB Sun Apr 26 23:04:49 2020
Thread-1拿到A锁,执行完actionA后到A锁释放。线程再一次竞争的时候,Thread-2拿到了A锁,Thread-1拿到了B锁。然后拿另一把时都在等待对方的锁释放,造成死锁状态
例子2,RLock 递归锁 相当于有个计数器,锁一次加1,大于1时都别人不能拿到。
import threading import time class MyThread(threading.Thread): def actionA(self): r_lock.acquire() print(self.name,"gotA",time.ctime()) time.sleep(2) r_lock.acquire() print(self.name,"gotB",time.ctime()) time.sleep(1) r_lock.release() r_lock.release() def actionB(self): r_lock.acquire() print(self.name, "gotB", time.ctime()) time.sleep(2) r_lock.acquire() print(self.name, "gotA", time.ctime()) time.sleep(1) r_lock.release() r_lock.release() def run(self): self.actionA() self.actionB() if __name__=="__main__": r_lock=threading.RLock() l=[] for i in range(5): t=MyThread() t.start() l.append(t)
5、同步对象(Event)
event方法
# event=threading.Event() # event.wait() # event.set() # event.clear()
例子1
import threading import time class Boss(threading.Thread): def run(self): print("BOSS:今晚加班到22:00") print(event.isSet())#False event.set() time.sleep(5) print("BOSS:<22:00>可以下班了") print(event.isSet()) event.set() class Worker(threading.Thread): def run(self): event.wait() #等待被set,一旦被设定,等同于pass print("Worker:唉。。。命苦啊!") time.sleep(1) event.clear() event.wait() print("Worker:万岁") if __name__=="__main__": event=threading.Event() thread=[] for i in range(5): thread.append(Worker()) thread.append(Boss()) for i in thread: i.start() for i in thread: i.join() print("ending.......")
6、信号量(Semaphore)
信号量允许同时多个线程进入,是锁的一种
用来控制线程并发数的,信号量管理一个内置的计数器,每当调用acquire()是-1,调用release()时+1
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
例子1
import threading import time class myThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(3) semaphore.release() if __name__=="__main__": semaphore=threading.Semaphore(5)#表示同时可以开5个,默认是1 thrs=[] for i in range(100): thrs.append(myThread()) for t in thrs: t.start()
7、队列
队列:队列是一种数据结构,列表是不安全的数据结构
Queue模块三种队列及构造函数
1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize)
例子1
import queue q=queue.Queue()#默认先进先出FIFO # q=queue.Queue(0)#表示队列大小为3,如果maxsize小于1就表示队列长度无限 # q=queue.Queue(maxsize=3) q.put(12) #put()方法在队尾插入一个项目 q.put("hello") q.put({"name":"yuan"}) # q.put(34,False) #报错队列满 while 1: data=q.get()#将一个值从队列中取出 # data=q.get(block=False) 报错队列空 print(data) print("______")
结果为:
12 ______ hello ______ {'name': 'yuan'} ______
例子2,后进先出
import queue q=queue.LifoQueue() q.put(12) q.put("hello") q.put({"name":"yuan"}) while 1: data=q.get() print(data) print("______")
例子3,按优先级
import queue q=queue.PriorityQueue() q.put([3,12]) q.put([2,"hello"]) q.put([4,{"name":"yuan"}]) while 1: data=q.get() print(data[1]) print("______")
其他方法
import queue q=queue.PriorityQueue()#默认先进先出FIFO q.put([3,12]) q.put([2,"hello"]) q.put([4,{"name":"yuan"}]) print(q.qsize())#返回队列大小 3 print(q.empty()) #是否为空 False print(q.full())#是否是满 False # q.put_nowait(56)#相当于q.put(item,False) # q.get_nowait()#相当于q.get(False) # q.task_done()#在完成一项工作后,向任务已经完成的队列发送一个信号 # q.join()#等待队列为空,再执行别的操作 while 1: data=q.get() print(data[1]) print("______")
8、生产者消费者模型
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
例子:
import threading import time import random import queue q=queue.Queue() def Producer(name): count=0 while count<10: print("making...") time.sleep(3) q.put(count) print("Producer %s has produced %s baozi.."%(name,count)) count+=1 q.join() print("ok....") def Consumer(name): count=0 while count<10: time.sleep(random.randrange(4)) data=q.get() print("eating...") time.sleep(4) q.task_done() print("\033[32;1mConsumer %s has eat %s baozi ... \033[0m" %(name,data)) count+=1 p1=threading.Thread(target=Producer,args=("A君",)) c1=threading.Thread(target=Consumer,args=("B君",)) c2=threading.Thread(target=Consumer,args=("C君",)) p1.start() c1.start() c2.start()
二、进程
由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。
multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。
进程的调用
例子1,调用方式1
from multiprocessing import Process import time def f(name): time.sleep(1) print("hello",name,time.ctime()) if __name__=="__main__": p_list=[] for i in range(3): p=Process(target=f,args=("steven",)) p_list.append(p) p.start() for i in p_list: i.join() print("ending")
例子2,调用方式2
from multiprocessing import Process import time class MyProcess(Process): def run(self): time.sleep(1) print("hello",self.name,time.ctime()) if __name__=="__main__": p_list=[] for i in range(3): p=MyProcess() # p.daemon=True#守护进程 p.start() p_list.append(p) for p in p_list: p.join() print("end....")
例子3,其他的方法
from multiprocessing import Process import time import os def info(title): print("title:",title) print("parent process:",os.getppid()) #父进程ID print("process id",os.getpid()) #进程ID def f(name): info("function f") print("hello",name) if __name__=="__main__": info("main process") time.sleep(1) print("-----") p=Process(target=info,args=("a",)) p.start() p.join()
结果为:
title: main process parent process: 10280 process id 1116 ----- title: a parent process: 1116 process id 7220
Process类
构造方法:
group: 线程组,目前还没有实现,库引用中提示必须是None
target: 要执行的方法
name: 进程名
args/kwargs: 要传入方法的参数
实例方法:
is_alive(): 返回进程是否在运行
join([timeout]): 阻塞当前上下文环境的进程,直到调用此方法的进程终止或者到达指定的timeout
start(): 进程准备就绪等待cpu调度
run(): start()调用run方法。
terminate(): 不管任务是否完成,立即停止工作进程
属性:
daemon: 和线程的setDaemon功能一样
name: 进程名字
pid: 进程号
进程间通信
例子1:进程队列
import time import multiprocessing def foo(q): time.sleep(1) q.put(123) q.put("yuan") if __name__=="__main__": q=multiprocessing.Queue()#创建进程队列 p=multiprocessing.Process(target=foo,args=(q,)) p.start() print(q.get()) print(q.get())
使用多进程消耗大,因为使用的资源会拷贝到其他进程
例子2:管道
from multiprocessing import Process,Pipe def f(conn): conn.send([12,{"name":"jobs"},"hello"]) response=conn.recv() print("response:",response) conn.close() print("q_ID2",id(conn)) if __name__=="__main__": parent_conn,child_conn=Pipe()#双向管道 print("q_ID1:",id(child_conn)) p=Process(target=f,args=(child_conn,)) p.start() print(parent_conn.recv()) parent_conn.send("你好!") p.join()
结果为:
q_ID1: 2043761525704 [12, {'name': 'jobs'}, 'hello'] response: 你好! q_ID2 1886540282176
例子3:Manager
Queue和pipe只是实现了数据交互,并没有实现数据共享,即一个进程去更改另一个进程的数据
manager()返回的一个manager对象控制一个服务进程,该进程具有的Python对象 并允许其他进程使用代理进行操作
manager()返回的一个manager对象支持的类型有:list
, dict
, Namespace
, Lock
, RLock
, Semaphore
, BoundedSemaphore
, Condition
, Event
, Barrier
, Queue
, Value
and Array
.
from multiprocessing import Process,Manager def f(d,l,n): d[n]="1" d["2"]=2 l.append(n) # print("son process:",id(d),id(l)) if __name__=="__main__": with Manager() as manager: d=manager.dict() #{} l=manager.list(range(5))#[0,1,2,3,4] p_list=[] for i in range(10): p=Process(target=f,args=(d,l,i)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l)
结果为:
{0: '1', '2': 2, 1: '1', 2: '1', 3: '1', 4: '1', 5: '1', 7: '1', 6: '1', 8: '1', 9: '1'} [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 7, 6, 8, 9]
进程同步
例子:
#场景1 未加锁 from multiprocessing import Process def f(i): print("hello world %s" %i) if __name__=="__main__":for num in range(10): Process(target=f,args=(num,)).start()
#未加锁时,在python2中命令行执行会出现串行,因为共用的是一个屏幕资源。
#场景2 加锁 from multiprocessing import Process, Lock def f(l, i): l.acquire() print("hello world %s" % i) l.release()#加锁部分就是串行的 if __name__ == "__main__": lock = Lock() for num in range(10): Process(target=f, args=(lock, num)).start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply
- apply_async
例子1
from multiprocessing import Process,Pool import time def Foo(num): time.sleep(1) print(num) if __name__=="__main__": pool=Pool(5) for i in range(50): pool.apply_async(func=Foo,args=(i,)) #pool.apply(func=Foo,args=(i,)) 同步接口 pool.close()#没有close,报错 pool.join() #进程池 join要放在close后面,没有join不会运行进程池 print("end")
例子2,使用回调函数
回调函数:就是某个动作或者函数执行成功后再去执行的函数
回调函数是主进程调用的
from multiprocessing import Process,Pool import time def Foo(num): time.sleep(1) print(num) return "hello %s"%num def Bar(arg): print(arg) if __name__=="__main__": pool=Pool(5) #没有参数时,默认的就是cpu的核心数 for i in range(50): pool.apply_async(func=Foo,args=(i,),callback=Bar) #执行成功后回调Bar函数 #Foo执行的返回值就当成回调函数Bar的参数 pool.close() pool.join() print("end")
三、协程
协程:协作式 ------非抢占式的程序
协程主要解决的也是IO操作的
协程:本质上就是一个线程
协程的优势:
1、没有切换的消耗
2、没有锁的概念
有个问题:能用多核吗?不能,可以采用多进程+协程,一个很好的解决方案
例子1,yield方式实现
import time def consumer(name): print("--->ready to eat baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name,new_baozi)) #time.sleep(1) def producer(): r = con.__next__() r = con2.__next__() n = 0 while 1: time.sleep(1) print("\033[32;1m[producer]\033[0m is making baozi %s and %s" %(n,n+1) ) con.send(n) con2.send(n+1) n +=2 if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") p = producer()
例子2、使用Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()
结果为:
12 56 34 78
例子3,gevent
import requests import gevent import time start=time.time() def f(url): resp=requests.get(url) data=resp.text print("%d bytes received form %s." %(len(data),url)) gevent.joinall([ gevent.spawn(f,"https://www.python.org/"), gevent.spawn(f,"https://www.yahoo.com/"), gevent.spawn(f,"https://www.baidu.com/"), gevent.spawn(f,"https://www.sina.com.cn/") ]) # f("https://www.python.org/") # f("https://www.yahoo.com/") # f("https://www.baidu.com/") # f("https://www.sina.com.cn/") print("cost time",time.time()-start)
补充:
任务:IO密集型
计算密集型
对IO密集型的任务:python的多线程是有意义的
可以采用多进程+协程
对于计算密集型的任务:python的多线程就不推荐,python就不适用