大小孩

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

本节内容

1.进程、线程定义和区别

2.Python GIL全局解释器锁

3.多线程

1.语法

2.join

3.线程锁之Lock\Rlock\信号量

4.将线程变为守护进程

5.Event事件 

6.queue队列

7.生产者消费者模型

4.多进程

1.语法

2.进程间通讯

3.进程锁

4.进程池

5.协程

1.自己实现协程

2. 封装好的协程模块(greenletgevent

3. 协程应用示例(简单爬虫、SocketServer

一、进程线程

进程:程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。进程里包含对各种系统资源的调用,以及内存对各种系统资源的整合。(进程要操作cpu,必须要先创建至少一个线程)

线程:操作系统最小的调度单位,是一串指令的集合

区别:

  • 线程间共享内存空间,进程的内存空间是独立的
  • 同一个进程的线程之间可以直接交流,两个进程想要通信必须通过一个中间代理来实现。
  • 新线程很容易创建,创建新进程需要对其父进程进行克隆(所以启动速度方面线程快,但运行速度无可比性)
  • 一个线程可以控制和操作同一进程里的其他线程,但进程只能操作子进程。
  • 对一个父线程的修改可能会影响同一个进程下其他子线程,但对父进程修改对其子进程没有影响。

二、Python GIL全局解释器锁

GIL (Global Interpreter Lock)

对于CPython来说,无论计算机有多少核,同一时间只有一个线程在执行。之所以表面上看起来是多并发,其实是CPU不断进行上下文切换。

CPython线程调用了OS的原生线程接口(C语言写的),如果不加GIL,多线程修改同一份数据Python并不能控制,就会出现错误。

PyPy去掉了GIL,使用了JIT(Just-In-Time)技术,运行速度大大加快。

三、多线程(threading)

什么时候使用Python多线程(单线程上下文切换)

  • io操作不占用cpu,计算占用cpu。
  • Python多线程适合io操作密集型任务,不适合cpu操作密集型任务。

1.语法

  1. import threading  
  2.         
  3. def run(n):  
  4.     print("task ",n )  
  5.         
  6. for i in range(5):  
  7.     实例化线程,args的值必须以tuple形式传入,如(a,),逗号不能漏写  
  8.     t = threading.Thread(target=run,args=("t-%s" %i ,))  
  9.     t.start()       #启动线程 

2.join

  1. import threading,time  
  2.         
  3. def run(n):  
  4.     print("task ",n )  
  5.     time.sleep(1)  
  6.         
  7. for i in range(5):  
  8.     实例化线程,args的值必须以tuple形式传入,如(a,  
  9.     t = threading.Thread(target=run,args=("t-%s" %i ,))  
  10.     t.start()       #启动线程  
  11.     t.join()      #等待线程完成再继续,程序变成串行  

3.将线程变为守护进程

  • 主线程等待所有非守护线程完成,主线程默认在最后有一个隐性的join()。
  • 主线程不等待守护线程完成(守护线程即为主线程提供服务的线程,主线程结束守护线程随之结束)。
  1. import threading,time  
  2.         
  3. def run(n):  
  4.     time.sleep(1)  
  5.     print("task ",n )  
  6.         
  7. for i in range(5):  
  8.     实例化线程,args的值必须以tuple形式传入,如(a,  
  9.     t = threading.Thread(target=run,args=("t-%s" %i ,))  
  10.     t.setDaemon(True)   #将线程设置为守护线程  
  11.     t.start()       #启动线程  

4.线程锁之Lock\Rlock\信号量

用户锁

GIL并没有对所有的线程共享资源加锁,视情况需要加用户锁(Python3已经做了优化,但是还是要加,官方并没有 声明说不需要)

参考 http://www.cnblogs.com/alex3714/articles/5230609.html GIL VS Lock

RLOCK(递归锁)

锁中还有锁,每道锁和钥匙之间以字典形式做对应,防止混淆

  1. import threading  
  2.       
  3. def run1():  
  4.     print("grab the first part data")  
  5.     lock.acquire()  
  6.     global num  
  7.     num += 1  
  8.     lock.release()  
  9.     return num  
  10.       
  11. def run2():  
  12.     print("grab the second part data")  
  13.     lock.acquire()  
  14.     global num2  
  15.     num2 += 1  
  16.     lock.release()  
  17.     return num2  
  18.       
  19. def run3():  
  20.     lock.acquire()  
  21.     res = run1()  
  22.     print('--------between run1 and run2-----')  
  23.     res2 = run2()  
  24.     lock.release()  
  25.     print(res, res2)  
  26.       
  27. num, num2 = 0, 0  
  28. lock = threading.RLock()        #使用递归锁  
  29. for i in range(1):  
  30.     t = threading.Thread(target=run3)  
  31.     t.start()  
  32.       
  33. while threading.active_count() != 1:  
  34.     print(threading.active_count())  
  35. else:  
  36.     print('----all threads done---')  
  37.     print(num, num2)  

线程锁(互斥锁Mutex)

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,如果2个线程同时要修改同一份数据,结果可能会发生错误。

  1. import threading  
  2. import time  
  3.       
  4. def run(n):  
  5.     lock.acquire()  #修改数据前加锁  
  6.     global  num     #在每个线程中都获取这个全局变量  
  7.     num +=1  
  8.     time.sleep(1)  
  9.     lock.release()      #解锁  
  10.       
  11. lock = threading.Lock()       #python2.x上修改count没有GIL,只是做了count的复制,所以要加用户锁  
  12. num = 0  
  13. t_objs = [] #存线程实例  
  14. for i in range(50):  
  15.     t = threading.Thread(target=run,args=("t-%s" %i ,))  
  16.     t.start()  
  17.     t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里  
  18.       
  19. for t in t_objs: #循环线程实例列表,等待所有线程执行完毕  
  20.     t.join()  
  21.       
  22. print("----------all threads has finished...",threading.current_thread(),threading.active_count())  
  23.       
  24. print("num:",num) 

信号量(Semaphore)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据。可以用来限制 socketserver同一时间内允许连接数。

  1. import threading, time  
  2.       
  3. def run(n):  
  4.     semaphore.acquire()  
  5.     time.sleep(1)  
  6.     print("run the thread: %s\n" % n)  
  7.     semaphore.release()  
  8.       
  9. if __name__ == '__main__':  
  10.     semaphore = threading.BoundedSemaphore(5)  最多允许5个线程同时运行  
  11.     for i in range(22):  
  12.         t = threading.Thread(target=run, args=(i,))  
  13.         t.start()  
  14. while threading.active_count() != 1:  
  15.     pass  # print threading.active_count()  
  16. else:  
  17.     print('----all threads done---')  
  18.     #print(num)  

5.Event事件 

通过Event来实现两个或多个线程间的交互

  1. import time  
  2. import threading  
  3.       
  4. event = threading.Event()     #实例化  
  5.       
  6. def lighter():  
  7.     count = 0  
  8.     event.set()         #设置标志位,绿灯  
  9.     while True:  
  10.         if count >5 and count < 10: #改成红灯  
  11.             event.clear()   #清空标志位  
  12.             print("\033[41;1mred light is on....\033[0m\r")  
  13.         elif count >10:  
  14.             event.set()     #变绿灯  
  15.             count = 0  
  16.         else:  
  17.             print("\033[42;1mgreen light is on....\033[0m\r")  
  18.         time.sleep(1)  
  19.         count +=1  
  20.       
  21. def car(name):  
  22.     while True:  
  23.         if event.is_set():  #判断标志位是否设定,代表绿灯  
  24.             print("[%s] running..."% name )  
  25.             time.sleep(1)  
  26.         else:  
  27.             print("[%s] sees red light , waiting...." %name)  
  28.             event.wait()    #标志位被set什么都不做,标志位被clear等待set  
  29.             print("\033[34;1m[%s] green light is on, start going...\033[0m" %name)  
  30.       
  31. light = threading.Thread(target=lighter,)  
  32. light.start()  
  33.       
  34. car1 = threading.Thread(target=car,args=("Tesla",))  
  35. car1.start() 

6.queue队列

  • class queue.Queue(maxsize=0)     #先入先出,maxsize为队列大小
  • class queue.LifoQueue(maxsize=0)         #last in fisrt out
  • class queue.PriorityQueue(maxsize=0)     #存储数据时可设置优先级的队列

作用

  • 程序的解耦
  • 提高效率

与列表区别

  • 队列数据取出来就没了

示例1

  1. import queue    
  2.           
  3. q  = queue.LifoQueue()  #后入先出  
  4.           
  5. q.put(1)        #向队列中加数据    
  6. q.put(2)    
  7. q.put(3)    
  8. print('size',q.qsize())   #队列大小    
  9. print(q.get())   #get(self, block=True, timeout=None),默认取不到block即阻塞    
  10. print(q.get())    
  11. print(q.get())    
  12. print(q.get_nowait())   #若取不到抛出异常queue.Empty

示例2

  1. import queue    
  2.           
  3. q = queue.PriorityQueue()  #优先级高的先取出    
  4.           
  5. q.put((-1,"chenronghua"))    
  6. q.put((3,"hanyang"))    
  7. q.put((10,"alex"))    
  8. q.put((6,"wangsen"))    
  9.           
  10. print(q.get())    
  11. print(q.get())    
  12. print(q.get())    
  13. print(q.get())  

Queue.task_done()

     

7.生产者消费者模型

作用

  • 解耦,调整生产者不影响消费者
  1. import threading,time    
  2. import queue    
  3.           
  4. q = queue.Queue(maxsize=10)    
  5.           
  6. def Producer(name):    
  7.     count = 1    
  8.     while True:    
  9.         q.put("骨头%s" % count)    
  10.         print("生产了骨头",count)    
  11.         count +=1    
  12.         time.sleep(0.1)    
  13.           
  14. def  Consumer(name):    
  15.     #while q.qsize()>0:    
  16.     while True:    
  17.         print("[%s] 取到[%s] 并且吃了它..." %(name, q.get()))    
  18.         time.sleep(1)    
  19. p = threading.Thread(target=Producer,args=("Alex",))    
  20. c = threading.Thread(target=Consumer,args=("ChengRonghua",))    
  21. c1 = threading.Thread(target=Consumer,args=("王森",))    
  22.           
  23. p.start()    
  24. c.start()    
  25. c1.start() 

四、多进程(multiprocessing)

CPython折衷利用CPU多核方法

启动多个进程,进程没有GIL的概念,计算机允许同时有与CPU核数量相同的进程运行。每个进程至少一个线程。

缺点:进程间内存是独立的,数据默认情况下不能共享

1.语法

  1. from multiprocessing import Process    
  2. import os    
  3.           
  4. def info(title):    
  5.     print(title)    
  6.     print('module name:', __name__)    
  7.     print('parent process:', os.getppid())  #os.getppid()父进程pid    
  8.     print('process id:', os.getpid())   #os.getpid()进程pid    
  9.     print("\n\n")    
  10.           
  11. def f(name):    
  12.     info('\033[31;1mcalled from child process function f\033[0m')    
  13.     print('hello', name)    
  14.           
  15. if __name__ == '__main__':    
  16.     info('\033[32;1mmain process line\033[0m')    
  17.     p = Process(target=f, args=('bob',))  #建立子进程        
  18.     p.start()   #启动子进程,每一个进程都是由父进程启动的    
  19.     # p.join()    
  20.           
  21. '''''''  
  22. main process line  
  23. module name: __main__  
  24. parent process: 9752  
  25. process id: 10908  
  26.         
  27. called from child process function f  
  28. module name: __mp_main__  
  29. parent process: 10908  
  30. process id: 11224  
  31.         
  32. hello bob  
  33. ''' 

2.进程间通讯

不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

进程Queue

使用方法跟threading里的queue差不多。主进程启动一个子进程,并且主进程建立一个Queue传给子进程,相当于把 Queue克隆一份传给子进程,对其中一个Queue进行操作时会自动通过pickle序列化与反序列化同步改变。

  1. from multiprocessing import Process, Queue    
  2. import threading    
  3. #import queue   #线程queue,线程间可以直接共享    
  4.           
  5. # def f(q):    
  6. #     q.put([42, None, 'hello'])    
  7.           
  8. def f(qq):    
  9.     print("in child:",qq.qsize())    
  10.     qq.put([42, None, 'hello'])    
  11.           
  12. if __name__ == '__main__':    
  13.     q = Queue()       #只有进程queue才能传给子进程    
  14.     q.put("test123")    
  15.     #p = threading.Thread(target=f,)    
  16.     p = Process(target=f, args=(q,)) #需要传给子进程(实际上是克隆并用pickle同步的过程)    
  17.     p.start()    
  18.     p.join()    
  19.     print("444",q.get_nowait())    
  20.     print("444",q.get_nowait())    
  21.     #prints "[42, None, 'hello']"    
  22.     #print(q.get())  #"[42, None, 'hello']"  

管道Pipe

像socket一样需要通信的两个进程间建立一对连接,互相发送消息。

注:相邻两次发送或接收数据是独立的,不会合并。

  1. from multiprocessing import Process, Pipe    
  2.           
  3. def f(conn):    
  4.     conn.send([42, None, 'hello from child'])    
  5.     conn.send([42, None, 'hello from child2'])    
  6.     print("from parent:",conn.recv())    
  7.     conn.close()    
  8.           
  9. if __name__ == '__main__':    
  10.     parent_conn, child_conn = Pipe()  #建立成对的连接,像一对传声筒一样  
  11.     p = Process(target=f, args=(child_conn,)) #将其中一个传给要通信的进程   
  12.     p.start()    
  13.     print(parent_conn.recv())  # prints "[42, None, 'hello']"    
  14.     print(parent_conn.recv())  # prints "[42, None, 'hello']"    
  15.     parent_conn.send("张洋可好"# prints "[42, None, 'hello']"    
  16.     p.join()

Manager

相当于优化版的Queue,直接生成需要在进程间共享的数据类型即可,其他隐藏的拷贝给其他进程、合并数据等操作自动完成(已经默认加锁,同一时间只有一个进程修改数据)。

  1. from multiprocessing import Process, Manager    
  2. import os    
  3.           
  4. def f(d, l):    
  5.     d[os.getpid()] =os.getpid()    
  6.     l.append(os.getpid())    
  7.     print(l)    
  8.           
  9. if __name__ == '__main__':    
  10.     with Manager() as manager:    
  11.         d = manager.dict() #{} #生成一个字典,可在多个进程间共享和传递    
  12.           
  13.         l = manager.list(range(5))#生成一个列表,可在多个进程间共享和传递    
  14.         p_list = []    
  15.         for i in range(10):    
  16.             p = Process(target=f, args=(d, l))    
  17.             p.start()    
  18.             p_list.append(p)    
  19.         for res in p_list: #等待结果    
  20.             res.join()    
  21.           
  22.         print(d)    
  23.         print(l)

3.进程锁

父进程生成锁,并传递给子进程

存在意义:对于多个进程共享的资源需要加锁,如多个进程共享一块屏幕,在屏幕上输出时需要加锁,防止输出混乱。

  1. from multiprocessing import Process, Lock    
  2.           
  3. def f(l, i):    
  4.     l.acquire()    
  5.     print('hello world', i)    
  6.     l.release()    
  7.           
  8. if __name__ == '__main__':    
  9.     lock = Lock()    
  10.     for num in range(100):    
  11.         Process(target=f, args=(lock, num)).start()  

4.进程池

线程由于系统资源开销非常小,所有一般不需要限制最大线程数量。线程过多对计算机唯一可能造成不好的影响是CPU上下文切换过于频繁,导致系统运行速度变慢。

与线程相对比,进程的系统资源开销大,如果进程过多有可能会搞瘫系统,所以要引入线程池,限制同一时间运行的进程数量。

  1. from  multiprocessing import Process, Pool,freeze_support    
  2. import time    
  3. import os    
  4.           
  5. def Foo(i):    
  6.     time.sleep(2)    
  7.     print("in process",os.getpid())    
  8.     return i + 100    
  9.           
  10. def Bar(arg):    
  11.     print('-->exec done:', arg,os.getpid())    
  12.           
  13. if __name__ == '__main__':      #windows上必须加这一句    
  14.     #freeze_support()    
  15.     pool = Pool(processes=3) #允许进程池同时放入5个进程    
  16.     print("主进程",os.getpid())    
  17.     for i in range(10):    
  18.         pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回调,子进程执行完了Foo  
  19.                   #主进程执行Bar。回调减少了子进程多余的操作,同类操作主进程执行就可以(如连接数据库)  
  20.         #pool.apply(func=Foo, args=(i,))    #串行(同步)          
  21.         #pool.apply_async(func=Foo, args=(i,)) #并行(异步)    
  22.     print('end')    
  23.     pool.close() #必须先closejoin    
  24.     pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。.join()    

五、协程

协程,又称微线程、纤程。英文名Coroutine。协程是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

协程可以实现单线程下的多并发。其原理是单线程同时处理多个连接,当遇到IO操作时就切换到其他连接,并且告诉OS如果IO操作完成就调用回调函数通知它切换回来(未接收到通知就不断轮循切换连接)

1.自己实现协程

  1. import time    
  2. import queue    
  3.           
  4. def consumer(name):     #生成器(调用时才生成,只记录当前值,只有一个next方法)    
  5.     print("--->starting eating baozi...")    
  6.     while True:    
  7.         new_baozi = yield    
  8.         print("[%s] is eating baozi %s" % (name, new_baozi))    
  9.         # time.sleep(1)    
  10.           
  11. def producer():    
  12.     r = con.__next__()  #调用生成器    
  13.     r = con2.__next__()    
  14.     n = 0    
  15.     while n < 5:    
  16.         n += 1    
  17.         con.send(n)     #给生成器传值    
  18.         con2.send(n)    
  19.         time.sleep(1)    
  20.         print("\033[32;1m[producer]\033[0m is making baozi %s" % n)    
  21.           
  22. if __name__ == '__main__':    
  23.     con = consumer("c1")    
  24.     con2 = consumer("c2")    
  25.     p = producer() 

2.封装好的协程模块(greenlet、gevent)

greenlet(手动在协程间切换)

  1. from greenlet import greenlet    
  2.           
  3. def test1():    
  4.     print(12)    
  5.     gr2.switch()    
  6.     print(34)    
  7.     gr2.switch()    
  8. def test2():    
  9.     print(56)    
  10.     gr1.switch()    
  11.     print(78)    
  12.           
  13. gr1 = greenlet(test1) #启动一个协程    
  14. gr2 = greenlet(test2)    
  15. gr1.switch()    #手动切换到协程gr1  

gevent(是对greenlet的封装,自动切换)

  1. import gevent    
  2.           
  3. def foo():    
  4.     print('Running in foo')    
  5.     gevent.sleep(2)     #gevent内部方法,模拟遇到IO操作(自动切换到其他协程)   
  6.     print('Explicit context switch to foo again')    
  7. def bar():    
  8.     print('Explicit精确的 context内容 to bar')    
  9.     gevent.sleep(1)    
  10.     print('Implicit context switch back to bar')    
  11. def func3():    
  12.     print("running func3 ")    
  13.     gevent.sleep(0)    
  14.     print("running func3  again ")    
  15.               
  16. #生成协程    
  17. gevent.joinall([    
  18.     gevent.spawn(foo),     
  19.     gevent.spawn(bar),    
  20.     gevent.spawn(func3),    
  21. ])    

3.协程应用示例(简单爬虫、SocketServer)

简单爬虫示例

默认gevent检测不到urllib、socket等做了io操作,需要打补丁gevent.monkey.patch_all()  

  1. from urllib import request      #urllib简单的爬虫模块    
  2. import gevent,time    
  3. from gevent import monkey   #火眼金睛    
  4. monkey.patch_all() #把当前程序的所有的io操作给我单独的做上标记,默认gevent检测不到urllib(socket)做了io操作    
  5.           
  6. def f(url):    
  7.     print('GET: %s' % url)    
  8.     resp = request.urlopen(url)    
  9.     data = resp.read()    
  10.     print('%d bytes received from %s.' % (len(data), url))    
  11.           
  12. urls = ['https://www.python.org/',    
  13.         'https://www.yahoo.com/',    
  14.         'https://github.com/' ]    
  15. time_start = time.time()    
  16. for url in urls:    
  17.     f(url)    
  18. print("同步cost",time.time() - time_start)    
  19. async_time_start = time.time()    
  20. gevent.joinall([    
  21.     gevent.spawn(f, 'https://www.python.org/'),    
  22.     gevent.spawn(f, 'https://www.yahoo.com/'),    
  23.     gevent.spawn(f, 'https://github.com/'),    
  24. ])    
  25. print("异步cost",time.time() - async_time_start)  

socketserver示例

  1. import socket    
  2. import gevent    
  3. from gevent import socket, monkey    
  4. monkey.patch_all()  #把当前程序的所有的io操作给我单独的做上标记    
  5.           
  6. def server(port):    
  7.     s = socket.socket()    
  8.     s.bind(('0.0.0.0', port))    
  9.     s.listen(500)    
  10.     while True:    
  11.         cli, addr = s.accept()    
  12.         gevent.spawn(handle_request, cli)    
  13.           
  14. def handle_request(conn):    
  15.     try:    
  16.         while True:    
  17.             data = conn.recv(1024)    
  18.             print("recv:", data)    
  19.             conn.send(data)    
  20.             if not data:    
  21.                 conn.shutdown(socket.SHUT_WR)    
  22.           
  23.     except Exception as  ex:    
  24.         print(ex)    
  25.     finally:    
  26.         conn.close()    
  27.           
  28.           
  29. if __name__ == '__main__':    
  30.     server(8001) 

参考:

http://www.cnblogs.com/alex3714/articles/5230609.html

http://www.cnblogs.com/alex3714/articles/5248247.html

posted on 2017-12-20 23:01  大小孩  阅读(152)  评论(0)    收藏  举报