网站地图 | RSS订阅 老铁博客 - 上海SEO优化|上海网站建设|蜘蛛池出租|站群代搭建
你的位置:首页 » 前端脚本 » 正文

python多线程,python多线程实现

2020-2-24 14:42:46 | 作者:老铁SEO | 0个评论 | 人浏览

  因为Python中臭名昭著的GIL。


  那么GIL是什么?为什么会有GIL?多线程真的是鸡肋吗?GIL可以去掉吗?带着这些问题,我们一起往下看,同时需要你有一点点耐心。


  多线程是不是鸡肋,我们先做个实验,实验非常简单,就是将数字“1亿”递减,减到0程序就终止,这个任务如果我们使用单线程来执行,完成时间会是多少?使用多线程又会是多少?showmethecode


  单线程


  在我的4核CPU计算机中,单线程所花的时间是6.5秒。可能有人会问,线程在哪里?其实任何程序运行时,默认都会有一个主线程在执行。(关于线程与进程这里不展开,我会单独开一篇文章)


  多线程


  创建两个子线程t1、t2,每个线程各执行5千万次减操作,等两个线程都执行完后,主线程终止程序运行。结果,两个线程以合作的方式执行是6.8秒,反而变慢了。按理来说,两个线程同时并行地运行在两个CPU之上,时间应该减半才对,现在不减反增。


  是什么原因导致多线程不快反慢的呢?


  原因就在于GIL,在Cpython解释器(Python语言的主流解释器)中,有一把全局解释锁(GlobalInterpreterLock),在解释器解释执行Python代码时,先要得到这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想获得CPU执行代码指令,就必须先获得这把锁,如果锁被其它线程占用了,那么该线程就只能等待,直到占有该锁的线程释放锁才有执行代码指令的可能。


  因此,这也就是为什么两个线程一起执行反而更加慢的原因,因为同一时刻,只有一个线程在运行,其它线程只能等待,即使是多核CPU,也没办法让多个线程「并行」地同时执行代码,只能是交替执行,因为多线程涉及到上线文切换、锁机制处理(获取锁,释放锁等),所以,多线程执行不快反慢。


  什么时候GIL被释放呢?


  当一个线程遇到I/O任务时,将释放GIL。计算密集型(CPU-bound)线程执行100次解释器的计步(ticks)时(计步可粗略看作Python虚拟机的指令),也会释放GIL。可以通过设置计步长度,查看计步长度。相比单线程,这些多是多线程带来的额外开销


  CPython解释器为什么要这样设计?


  多线程是为了适应现代计算机硬件高速发展充分利用多核处理器的产物,通过多线程使得CPU资源可以被高效利用起来,Python诞生于1991年,那时候硬件配置远没有今天这样豪华,现在一台普通服务器32核64G内存都不是什么司空见惯的事


  但是多线程有个问题,怎么解决共享数据的同步、一致性问题,因为,对于多个线程访问共享数据时,可能有两个线程同时修改一个数据情况,如果没有合适的机制保证数据的一致性,那么程序最终导致异常,所以,Python之父就搞了个全局的线程锁,不管你数据有没有同步问题,反正一刀切,上个全局锁,保证数据安全。这也就是多线程鸡肋的原因,因为它没有细粒度的控制数据的安全,而是用一种简单粗暴的方式来解决。


  这种解决办法放在90年代,其实是没什么问题的,毕竟,那时候的硬件配置还很简陋,单核CPU还是主流,多线程的应用场景也不多,大部分时候还是以单线程的方式运行,单线程不要涉及线程的上下文切换,效率反而比多线程更高(在多核环境下,不适用此规则)。所以,采用GIL的方式来保证数据的一致性和安全,未必不可取,至少在当时是一种成本很低的实现方式。


  那么把GIL去掉可行吗?


  还真有人这么干多,但是结果令人失望,在1999年GregStein和MarkHammond两位哥们就创建了一个去掉GIL的Python分支,在所有可变数据结构上把GIL替换为更为细粒度的锁。然而,做过了基准测试之后,去掉GIL的Python在单线程条件下执行效率将近慢了2倍。


  Python之父表示:基于以上的考虑,去掉GIL没有太大的价值而不必花太多精力。


  python多线程实现


  Python进阶(二十六)-多线程实现同步的四种方式


  临界资源即那些一次只能被一个线程访问的资源,典型例子就是打印机,它一次只能被一个程序用来执行打印功能,因为不能多个线程同时操作,而访问这部分资源的代码通常称之为临界区。


  锁机制


  threading的Lock类,用该类的acquire函数进行加锁,用realease函数进行解锁


  importthreadingimporttimeclassNum:


  def__init__(self):


  self.num=0


  self.lock=threading.Lock()defadd(self):


  self.lock.acquire()#加锁,锁住相应的资源


  self.num+=1


  num=self.num


  self.lock.release()#解锁,离开该资源


  returnnum


  n=Num()classjdThread(threading.Thread):


  def__init__(self,item):


  threading.Thread.__init__(self)


  self.item=itemdefrun(self):


  time.sleep(2)


  value=n.add()#将num加1,并输出原来的数据和+1之后的数据


  print(self.item,value)foriteminrange(5):


  t=jdThread(item)


  t.start()


  t.join()#使线程一个一个执行12345678910111213141516171819202122232425262728


  当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”(参见多线程的基本概念)。


  直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。


  信号量


  信号量也提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,知道有线程调用了release方法将内部计数器更新到大于1位置。


  importthreadingimporttimeclassNum:


  def__init__(self):


  self.num=0


  self.sem=threading.Semaphore(value=3)#允许最多三个线程同时访问资源


  defadd(self):


  self.sem.acquire()#内部计数器减1


  self.num+=1


  num=self.num


  self.sem.release()#内部计数器加1


  returnnum


  n=Num()classjdThread(threading.Thread):


  def__init__(self,item):


  threading.Thread.__init__(self)


  self.item=itemdefrun(self):


  time.sleep(2)


  value=n.add()


  print(self.item,value)foriteminrange(100):


  python多线程和多进程的区别


  GIL在Python中,由于历史原因(GIL),使得Python中多线程的效果非常不理想.GIL使得任何时刻Python只能利用一个CPU核,并且它的调度算法简单粗暴:多线程中,让每个线程运行一段时间t,然后强行挂起该线程,继而去运行其他线程,如此周而复始,直到所有线程结束.这使得无法有效利用计算机系统中的"局部性",频繁的线程切换也对缓存不是很友好,造成资源的浪费.据说Python官方曾经实现了一个去除GIL的Python解释器,但是其效果还不如有GIL的解释器,遂放弃.后来Python官方推出了"利用多进程替代多线程"的方案,在Python3中也有concurrent.futures这样的包,让我们的程序编写可以做到"简单和性能兼得".多进程/多线程+Queue一般来说,在Python中编写并发程序的经验是:计算密集型任务使用多进程,IO密集型任务使用多进程或者多线程.另外,因为涉及到资源共享,所以需要同步锁等一系列麻烦的步骤,代码编写不直观.另外一种好的思路是利用多进程/多线程+Queue的方法,可以避免加锁这样麻烦低效的方式.现在在Python2中利用Queue+多进程的方法来处理一个IO密集型任务.假设现在需要下载多个网页内容并进行解析,单进程的方式效率很低,所以使用多进程/多线程势在必行.我们可以先初始化一个tasks队列,里面将要存储的是一系列dest_url,同时开启4个进程向tasks中取任务然后执行,处理结果存储在一个results队列中,最后对results中的结果进行解析.最后关闭两个队列.下面是一些主要的逻辑代码.#-*-coding:utf-8-*-#IO密集型任务#多个进程同时下载多个网页#利用Queue+多进程#由于是IO密集型,所以同样可以利用threading模块importmultiprocessingdefmain():tasks=multiprocessing.JoinableQueue()results=multiprocessing.Queue()cpu_count=multiprocessing.cpu_count()#进程数目==CPU核数目create_process(tasks,results,cpu_count)#主进程马上创建一系列进程,但是由于阻塞队列tasks开始为空,副进程全部被阻塞add_tasks(tasks)#开始往tasks中添加任务parse(tasks,results)#最后主进程等待其他线程处理完成结果defcreate_process(tasks,results,cpu_count):for_inrange(cpu_count):p=multiprocessing.Process(target=_worker,args=(tasks,results))#根据_worker创建对应的进程p.daemon=True#让所有进程可以随主进程结束而结束p.start()#启动def_worker(tasks,results):whileTrue:#因为前面所有线程都设置了daemon=True,故不会无限循环try:task=tasks.get()#如果tasks中没有任务,则阻塞result=_download(task)results.put(result)#someexceptionsdonothandledfinally:tasks.task_done()defadd_tasks(tasks):forurlinget_urls():#get_urls()returnaurls_listtasks.put(url)defparse(tasks,results):try:tasks.join()exceptKeyboardInterruptaserr:print"Taskshasbeenstopped!"printerrwhilenotresults.empty():_parse(results)if__name__=='__main__':main()利用Python3中的concurrent.futures包在Python3中可以利用concurrent.futures包,编写更加简单易用的多线程/多进程代码.其使用感觉和Java的concurrent框架很相似(借鉴?)比如下面的简单代码示例defhandler():futures=set()withconcurrent.futures.ProcessPoolExecutor(max_workers=cpu_count)asexecutor:fortaskinget_task(tasks):future=executor.submit(task)futures.add(future)defwait_for(futures):try:forfutureinconcurrent.futures.as_completed(futures):err=futures.exception()ifnoterr:result=future.result()else:raiseerrexceptKeyboardInterruptase:forfutureinfutures:future.cancel()print"Taskhasbeencanceled!"printereturnresult总结要是一些大型Python项目也这般编写,那么效率也太低了.在Python中有许多已有的框架使用,使用它们起来更加高效.


  python多线程爬虫


  这个没有固定数值,需要根据你爬取目标的访问速度,还有你服务器的性能配置(内存,cpu)来调整。


  如果解决了您的问题请采纳!


  如果未解决请继续追问!


  无疑是python,爬虫是python最擅长的方面之一,有许多强大的爬虫库如scrapy。而node.js虽然也能做爬虫,但在处理多线程方面受到限制,这是硬伤。


  python多线程爬虫实例


  importtime


  importthreading


  importQueue


  classConsumer(threading.Thread):


  def__init__(self,queue):


  threading.Thread.__init__(self)


  self._queue=queue


  defrun(self):


  whileTrue:


  #queue.get()blocksthecurrentthreaduntil


  #anitemisretrieved.


  msg=self._queue.get()


  #Checksifthecurrentmessageis


  #the"PoisonPill"


  ifisinstance(msg,str)andmsg=='quit':


  #ifso,existstheloop


  break


  #"Processes"(orinourcase,prints)thequeueitem


  print"I'mathread,andIreceived%s!!"%msg


  #Alwaysbefriendly!


  print'Byebyes!'


  defProducer():


  #Queueisusedtoshareitemsbetween


  #thethreads.


  queue=Queue.Queue()


  #Createaninstanceoftheworker


  worker=Consumer(queue)


  #startcallstheinternalrun()methodto


  #kickoffthethread


  worker.start()


  #variabletokeeptrackofwhenwestarted


  start_time=time.time()


  #Whileunder5seconds..


  whiletime.time()-start_time<5:


  #"Produce"apieceofworkandstickitin


  #thequeuefortheConsumertoprocess


  queue.put('somethingat%s'%time.time())


  #Sleepabitjusttoavoidanabsurdnumberofmessages


  time.sleep(1)


  #Thisthe"poisonpill"methodofkillingathread.


  queue.put('quit')


  #waitforthethreadtoclosedown


  worker.join()


  if__name__=='__main__':


  Producer()<


  >

  • 本文来自: 老铁博客,转载请保留出处!欢迎发表您的评论
  • 相关标签:
  • 已有0位网友发表了一针见血的评论,你还等什么?

    必填

    选填

    记住我,下次回复时不用重新输入个人信息

    必填,不填不让过哦,嘻嘻。

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

    相关推荐