实验发现不涉及IO输入的多线程,串行运行在老的(新解释器缩短差距)解释器有时候比多线程快,这是什么原因?
GIL:全局解释锁(这玩意跟python语言无关,跟解释种类有关,只对CPython解释器有用,但是这种站主导市场)
因为有GIL,所以同一时刻,只有一个线程被一个CPU执行
多核对于Python多线程用不上,完了!!!
多进程可以用上多核,但是进程的开销大!!
总结:多线程用不上多核(计算密集型任务时,不如串行;IO密集型任务它还是不错的),多进程太多时开销大,所以用多进程+协程。
1 #!/usr/bin/env python 2 #-*-coding:utf-8 -*- 3 4 ''' 5 python 大都编辑器的单一进程中的多线程是无法用多核的,因为GIL;但是多进程可以,所以多进程模块multiprocessing 6 线程的调用两种方法,一是直接用treading模块,二是继承这个模块。进程相似(守护进程deamon有点区别) 7 如果有4个CPU,开4个进程,就可以实现真的并行,而不是并发 8 ''' 9 10 import time, multiprocessing 11 import threading 12 13 def f(name): 14 time.sleep(1) 15 print("hello",name,time.ctime()) 16 17 if __name__ == '__main__': 18 plist = [] 19 for i in range(2): 20 p = multiprocessing.Process(target=f,args=("wan",)) 21 # p = threading.Thread(target=f,args=('wan',)) 22 plist.append(p) 23 p.start() 24 25 for i in plist: 26 i.join() 27 28 print("ending........")
1 from multiprocessing import Process 2 import os 3 import time 4 5 6 def info(title): 7 print("title:", title) 8 print('parent process:', os.getppid()) # 每个进程有一个ID号,getppid是得到父进程id 9 print('process id:', os.getpid()) # getpid是得到自己当前运行的id 10 11 def f(name): 12 13 info('function f') 14 print('hello', name) 15 16 17 if __name__ == '__main__': 18 19 info('main process line') 20 21 time.sleep(1) 22 print("------------------") 23 p = Process(target=info, args=('yuan',)) # 这里的父进程的ID号应该是等于上面info调用的后等到的process id 一样 24 p.start() 25 p.join()
介绍一下multiprocessing.Process类:
is_alive() 返回进程是否运行
terminate () 不管任务是否完成,立即停止工作进程
进程之间的通讯,因为一个进程中的多个线程是在同一块进程的内存空间中,他们数据共享,很简单。但是进程之间的内存空间不同,要解决进程之间的通讯,以及共享数据问题(我改了数据,你拿到的应该是我改后的)。进程中涉及大量的数据拷贝,所以资源消耗大。
下面讲进程通讯:队列,管道(前两个只是完成通讯功能,没有实现数据共享),Managers(可实现数据共享)
队列:
说队列前,先介绍一下列表这种数据结构,看看它在线程中的缺陷,线程不安全。
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 li.remove(a) 11 12 t1 = threading.Thread(target=pri) 13 t2 = threading.Thread(target=pri) 14 15 t1.start() 16 t2.start() 17 18 t1.join() 19 t2.join() 20 print("end.............")
用了队列,刚才列表在线程中的问题就不存在了。
1 import queue 2 3 q = queue.Queue(3) # 先进先出,后进先出是.LifeoQueue,还可以按级别出.PriorityQueue 4 5 q.put(12) # put 与 get还有一些其他的参数 6 q.put("hello") 7 q.put({'name':"wan"}) 8 9 print(q.qsize()) # 队列中实际有多少值 10 print(q.empty()) # 是否为空 11 print(q.full()) # 是否为满 12 13 while 1: 14 data = q.get() 15 print(data) 16 print("--------------")
同样,用队列进行进程中的通讯如下:
1 import multiprocessing 2 # import queue 3 4 5 def foo(q): 6 7 q.put(123) 8 q.put("wan") 9 10 if __name__ == '__main__': 11 q = multiprocessing.Queue() # 进程中有自己的队列 12 plist = [] 13 for i in range(3): 14 p = multiprocessing.Process(target=foo,args=(q,)) 15 # 这里必须把参数q传进去,因为它不像线程那样共享,在主线程建立q,只是主进程内存空间中,子进程中队列都没定义 16 plist.append(p) 17 p.start() 18 print(q.get()) 19 print(q.get()) 20 print(q.get()) 21 print(q.get())
管道通讯:
1 from multiprocessing import Process, Pipe 2 def f(conn): 3 conn.send([12, {"name":"yuan"}, 'hello']) # 子进程发 4 response=conn.recv() # 子进程收 5 print("response",response) 6 conn.close() 7 # print("q_ID2:",id(conn)) 8 9 if __name__ == '__main__': 10 11 parent_conn, child_conn = Pipe() # 双向管道,类似电话的两头,一头给子进程,一头给主进程 12 13 # print("q_ID1:",id(child_conn)) 14 p = Process(target=f, args=(child_conn,)) 15 p.start() 16 17 print(parent_conn.recv()) # 父进程(主进程)接收到的 18 parent_conn.send("儿子你好!") # 父进程发 19 p.join()
Manager:
1 from multiprocessing import Process, Manager 2 3 def f(d, l,n): 4 5 d[n] = '1' #{0:"1"} 6 d['2'] = 2 #{0:"1","2":2} 7 8 l.append(n) #[0,1,2,3,4, 0,1,2,3,4,5,6,7,8,9] 9 #print(l) 10 11 12 if __name__ == '__main__': 13 14 with Manager() as manager: # 类似用with打开文件,省略关的功能 15 16 d = manager.dict() #{} 17 18 l = manager.list(range(5)) #[0,1,2,3,4] 19 20 21 p_list = [] 22 23 for i in range(10): 24 p = Process(target=f, args=(d,l,i)) 25 p.start() 26 p_list.append(p) 27 28 for res in p_list: 29 res.join() 30 31 print(d) 32 print(l)
#!/usr/bin/env python #-*-coding:utf-8 -*- ''' 线程有同步(同步锁):因为它们共享数据,可能会出错; 进程数据不共享,但是也共享一些资源(比如屏幕(进程1与进程2可能同一时刻想同时输出在屏幕上,也就是出现串行错误)) 因此,进程也需要同步multiprocessing.Lock ''' 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(12): Process(target=f,args=(lock, num)).start() print('ending.............')
1 ''' 2 进程池:控制开进程的最大量 multiprocessing.Pool 3 进程池两个方法:apply同步 , apply_async异步 4 5 回调函数:就是某个动作或者某个函数执行成功后再去执行的函数 6 感觉回调函数可以把内容写在它跟的函数后面,这样基本就等同了 7 但是callback是在主进程下面调用的 8 ''' 9 10 from multiprocessing import Process,Pool 11 import time,os 12 13 def f(i): 14 time.sleep(3) 15 print(i) 16 print("son pid: ",os.getpid()) 17 return 'hello %s'%i 18 19 def Bar(arg): # 回调函数必须至少有一个参数,就是它跟的函数的返回值 20 print(arg) 21 print('callback pid: ',os.getpid()) 22 23 if __name__ == '__main__': 24 pool = Pool(5) 25 print("main pid: ",os.getpid()) 26 for i in range(100): 27 pool.apply_async(func=f,args=(i,),callback=Bar) # callback是回调函数名字,它在每个子进程执行完后执行 28 29 pool.close() 30 pool.join() # 注意这个pool.close与pool.join的顺序是固定的