更多原创文章请移步https://www.365tofree.com/一、python的GIL(global interpreter Lock)全局解释器锁cpython概述python中的一个线程对应于C语言中的一个线程GIL使得同一时刻只有一个线程在一个cpu上执行字节码无法将多个线程映射到多个cpu上执行。GIL释放GIL会根据执行的字节码行数执行多少行释放以及时间片执行多少毫秒释放释放GILGIL在遇到IO的操作时主动释放。让我们来举个例子看一下吧number0defadd():globalnumberforiinrange(100000):number1defdesc():globalnumberforiinrange(100000):number-1importthreading func1threading.Thread(targetadd)func2threading.Thread(targetdesc)func1.start()func2.start()func1.join()func2.join()print(num)可以看到得出的结果是非常不稳定的也就是说python在运行的时候不是将一个函数完全运行完再去运行下一个函数。二、多线程编程-threading概述对于IO操作来说多线程和多进程性能差别不大。1、通过实例化Thread类实现多线程编程thread1.setDaemon(True) # setDaemon子进程成为守护进程当主进程关闭守护进程关闭。thread1.join() # join线程阻塞等线程运行完毕再结束退出。来吧让我们看一下例子# 多线程实现的一种方式,一下例子是模拟爬虫的importthreadingimporttimedefget_url(url):打开urlprint(start get url)time.sleep(2)print(end get url)defget_detail(name):获取网页的数据print(start get detail)time.sleep(4)print(end get detail)if__name____main__:thread1threading.Thread(targetget_url,args(,))# args是函数的参数此处是元组thread2threading.Thread(targetget_detail,args(,))# 当参数只有一个时需要加‘’thread1.setDaemon(True)# 此时thread1是守护进程当主进程结束不管子进程是否结束都会退出thread2.setDaemon(True)start_timetime.time()thread1.start()thread2.start()thread1.join()thread2.join()print(ltime: {}.format(time.time()-start_time))2、通过继承Thread来实现多线程编程classGetDetailHtml(threading.Thread):def__init__(self,name):super().__init(namename)defrun(self):print(start)time.sleep(2)print(end)if__name____main__:thraed1GetDetailHtml(test1)thread1.start()thread1.join()三、线程间的通信-共享变量和Queue1、共享变量global()与函数间的调用相似2、queue队列-线程间的通信更加安全defget_detail_html(queue):# 爬取文章详情whileTrue:urlqueue.get(queue)# 从队列中取出URL进行操作print(url start)time.sleep(2)print(url end)defget_detail_url(queue):# 爬取文章列表页whileTrue:print(html start)foriinrange(20):queue.put(http://projectdu.com/{id}.format(idi))# 将爬取的url加入到空队列中time.sleep(4)print(html end)if__name____main__:detail_url_queueQueue(maxsize1000)# maxsize队列最大值对内存有影响thread_detail_urlthreading.Thread(targetget_detail_url,args(detail_url_queue,))thread_detail_url.start()foriinrange(10):html_threadthreading.Thread(targetget_detail_html,args(detail_url_queue,))html_thread.start()detail_url_queue.task_done()# 发送停止信号detail_url_queue.join# 线程结束与task_done成对使用四、线程同步- Lock 、RLock1、Lock(1)加锁会使运行时间增加影响一定的性能(2) 锁会引起死锁fromtareadingimportLock total0lockLock()defadd():globaltotalgloballockforiinrange(10000):lock.acquire()# 先获取锁total1# 与desc中的减total互相竞争所以加锁防止错乱lock.release()# 一定要释放锁,不释放会导致其他竞争无法获取锁导致线程停滞defdesc():globallockglobaltotalforiinrange(10000):lock.acquire()# 先获取锁total-1lock.release()# 一定要释放锁importthreading thread1threading.Thread(targetadd)thread2threading.Thread(targetdesc)thread1.start()thread2.start()thread1.join()thread2.join()2、使用RLock在同一线程中可以多次调用acquire但是也要调用与acquire次数相同的release。lockRLock()defadd():globaltotalgloballockforiinrange(10000):lock.acquire()# 先获取锁lock.acquire()total1# 与desc中的减total互相竞争所以加锁防止错乱lock.release()lock.release()3、condition使用条件变量用于复杂的线程间通信同步(1) xiaoai.start()tianmao.start()启动顺序很重要一定要先启动最先处于等待状态的不能先启动发送通知状态的因为condition.wait()方法只能有condition.notify()方法能唤醒若先启动发送通知的类会一直处于等待状态2在调用with condition 之后才能调用wait或notify方法3condition有两层锁一把底层锁会在线程调用了wait方法的时候释放上面的锁会在每次调用wait的时候分配一把并放入到condition的等待队列中等待notify方法的唤醒。4分析文章请参考https://www.cnblogs.com/yoyoketang/p/8337118.htmlclassXiaoai(threading.Thread):def__init__(self,lock):super()__init__(name小爱)self.conditionconditiondefrun(self):withself.condition:self.condition.wait()# 等待天猫的一个通知接到通知开始说活print({}: 在 .format(self.name))self.condition.notify()# 小爱回答完发出一个通知给天猫self.condition.wait()print({}: 好啊 .format(self.name))self.condition.notify()classTianmao(threading.Thread):def__init__(self,lock):super()__init__(name天猫精灵)self.conditionconditiondefrun(self):withself.condition:print({}: 小爱同学 .format(self.name))self.condition.notify()# 天猫说完话进行一个通知给小爱self.condition.wait()等待小爱的通知进行回答print({}: 我们来对诗吧 .format(self.name))self.condition.notify()self.condition.wait()if__name____main__:conditionthreading.condition()xiaoaiXiaoai(condition)tianmaoTianmao(condition)xiaoai.start()tianmao.start()对话顺序详解对话以此类推天猫讲话完毕发通知给小爱此时的小爱处于等待状态接收到天猫的通知后小爱开始讲话讲话完毕发出通知给天猫此时的天猫处于等待状态接收到小爱的通知后开始讲话。。。。4、Semaphore信号量概述是用于控制进入数量的锁例 读写写入时候只允许一个写读的时候可以有多个读。classHtmlSpider(threading.Thread):def__init__(self,sem)super().__init__()self.semsemdefrun(self):time.sleep(2)print(got html text success)self.sem.release()classUrlProducer(threading.Thread):def__init__(self,sem)super().__init__()self.semsemdefrun(self):foriinrange(20):self.sem.acquire()# 对应一个release方法acquire控制线程的数量并且使用时会线程会-1相应的release释放后会线程1html_threadHtmlSpider(http://baidu.com/{}.format(i),self.sem)html_thread.start()if__name____main__:semthreading.Semaphore(3)# 开启三个线程url_prodecerUrlProducer()url_prodecer.start()5、线程池ThreadPoolExecutor(1)重要的包fromconcurrentimportfutures,as_completedwait2主线程中可以获取某一个线程的状态或者某一任务的状态以及返回值。当一个线程完成的时候我们的主线程能立即知道futures可以让多线程和多进程编码接口一致。(3)详细讲解https://www.jianshu.com/p/b9b3d66aa0beimporttimedefget_html(times):time,sleep(times)print(get page {} success.format(times))returntimes executorThreadPoolExecutor(max_workers2)# 通过submit函数提交执行的函数到线程池中submnit是立即执行task1executor.submit(get_html,(3))# 参数一是函数名称参数二是函数的参数task2executor.submit(get_html,(2))# 要获取已经成功的task的返回urls[3,2,4](2)all_task[executor.submit(get_html,(url))forurlinurls]wait(all_task)# wait方法可以控制等待某个事件完成后开始其他操作return_whenprint(all_task完成开始其他操作)forfutureinas_completed(all_task):# as_completed只返回完成的taskdatafuture.result()print(get {} page.format(data))(3)通过executor 的map获取已经完成的task的值fordatainexecutor.map(get_html,urls):# map方法就是要urls中的每个数去执行get_html方法print(get {} page.format(data))1print(task1.done())# done用于判定某个任务是否完成print(task1.result())# result方法可以获取task的执行结果五、多线程和多进程对比1、多进程编程消耗cpu的操作计算用多进程编程对于io操作来说进程切换代价要高于线程切换。2、对耗费cpu的操作多进程优于多线程。(验证多进程对于消耗cpu的操作时的性能是优于多线程的。)fromconcurrent.futuresimportThreadPoolExecutor,as_completedfromconcurrent.futuresimportProcessPoolExecutordeffib():ifn2:return1returnfib(n-1)fib(n-2)# 如下多线程运行withThreadPoolExecutor(3)asexecutor:all_task[executor.submit(fib,(num)fornuminrange(25,35)]start_timetime.time()forfutureinas_completed(all_task):datafuture.result()print(exe result: {} .format(data))print(last time is : {}.format(time.time()-start_time))# 如下多进程运行 注意A.多进程运行下若不在if __name__ __main__:下运行会报错仅在windows中 报错linux不会。 if__name____main__:withProcessPoolExecutor(3)asexecutor:all_task[executor.submit(fib,(num)fornuminrange(25,35)]start_timetime.time()forfutureinas_completed(all_task):datafuture.result()print(exe result: {} .format(data))print(last time is : {}.format(time.time()-start_time))3、对于IO操作来说多线程优于多进程使用sleep模拟iodefrandom_sleep(n):time.sleep(n)returnnif__name____main__:withThreadPoolExecutor(3)asexecutor:all_task[executor.submit(random_sleep,(num)fornumin[2]*30]start_timetime.time()forfutureinas_completed(all_task):datafuture.result()print(exe result: {} .format(data))print(last time is : {}.format(time.time()-start_time))六 、multiprocessing多进程编程1、多进程编程与多线程相似importtimeimportmultiprocessingdefget_html(n)time.sleep(n)print(process run success)returnnif__name____main__:processmultiprocessing.Process(targetget_html,args(2,))process.start()process.join()print(process run end)2、使用线程池poolmultiprocessing.Pool(multiprocessing.cpu_count())# cpu_count获取cup个数以cpu个数作为开启的进程个数resultpool.apply_async(get_html)未完待续。。。