纠纷奇闻社交美文家庭
投稿投诉
家庭城市
爱好生活
创业男女
能力餐饮
美文职业
心理周易
母婴奇趣
两性技能
社交传统
新闻范文
工作个人
思考社会
作文职场
家居中考
兴趣安全
解密魅力
奇闻笑话
写作笔记
阅读企业
饮食时事
纠纷案例
初中历史
说说童话
乐趣治疗

人人都能学会的Python多线程指南

12月24日 顾昀汐投稿
  来源:早起Python
  作者:刘早起
  在Python中,多线程最常见的一个场景就是爬虫,例如这样一个需求,有多个结构一样的页面需要爬取,例如下方的URL(豆瓣阿凡达影评,以10个为例)urllist〔https:movie。douban。comsubject1652587reviews?sorttimestart0,https:movie。douban。comsubject1652587reviews?sorttimestart20,https:movie。douban。comsubject1652587reviews?sorttimestart40,https:movie。douban。comsubject1652587reviews?sorttimestart60,https:movie。douban。comsubject1652587reviews?sorttimestart80,https:movie。douban。comsubject1652587reviews?sorttimestart100,https:movie。douban。comsubject1652587reviews?sorttimestart120,https:movie。douban。comsubject1652587reviews?sorttimestart140,https:movie。douban。comsubject1652587reviews?sorttimestart160,https:movie。douban。comsubject1652587reviews?sorttimestart180〕
  如果依次爬取,请求第一个页面得到返回数据解析数据提取、存储数据请求第二个页面,按照这样的思路,那么大量时间都会浪费在请求、返回数据上,如果在等待第一个页面返回数据时去请求第二个页面,就能有效的提高效率,多线程就可以实现这样的功能。
  在Python中实现多线程的方法也很多,我将基于threading模块一点一点介绍,注意本文不会太注重于多线程背后的技术概念(面试常问),仅希望用最少的话教会大家如何实现。当然会在最后介绍如何使用threading模块来解决上面的爬虫问题。threading基本使用
  让我们先从一个简单的例子开始,定义dosomething函数,执行该函数需要消耗1秒importtimestarttime。perfcounter()defdosomething():print(线程启动)time。sleep(1)print(线程结束)dosomething()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  上面的代码不难理解,执行dosomething并计算耗时,结果很明显应该是1s线程启动线程结束全部任务执行完成,耗时1。01秒
  现在如果需要执行两次dosomething,按照最基本的思路importtimestarttime。perfcounter()defdosomething():print(线程启动)time。sleep(1)print(线程结束)dosomething()dosomething()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  执行上面代码结果也很容易猜到是2秒线程启动线程结束线程启动线程结束全部任务执行完成,耗时2。01秒
  这就是最常规的同步思路,在CPU执行第一个函数,也就是等待1s的时间内,什么也不干,等第一个函数执行完毕后再执行第二个函数
  很明显,这样让CPU干等着啥也不干并不是一个很好的选择,而多线程就是解决这一问题的方法之一,让CPU在等待某个任务完成时去执行更多的操作,将整个过程简化为下图流程,这样就能充分节省时间
  现在使用threading来通过多线程的方式实现上面的过程,非常简单,定义两个线程并依次启动即可importtimeimportthreadingstarttime。perfcounter()defdosomething():print(线程启动)time。sleep(1)print(线程结束)thread1threading。Thread(targetdosomething)thread2threading。Thread(targetdosomething)thread1。start()thread2。start()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  执行上面的代码,结果如下线程启动线程启动全部任务执行完成,耗时0。0秒线程结束线程结束
  可以看到,两个子线程确实同时启动,但是主线程并未等待两个子线程执行完毕就直接结束。
  为了解决这个问题,我们可以使用threading。join()方法,意思是在子线程完成运行之前,这个子线程的父线程将一直被阻塞
  换成人话就是让主线程挂起,等待所有子线程结束再执行,体现到代码上也很简单,只需要添加两行即可importtimeimportthreadingstarttime。perfcounter()defdosomething():print(线程启动)time。sleep(1)print(线程结束)thread1threading。Thread(targetdosomething)thread2threading。Thread(targetdosomething)thread1。start()thread2。start()thread1。join()thread2。join()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  运行结果如下,全部代码在1秒内运行完毕线程启动线程启动线程结束线程结束全部任务执行完成,耗时1。01秒
  至此,我们就得到了第一个有效的多线程代码,相信你也能大致明白threading的基本使用流程。传递参数
  现在来看看如何在多线程之间传递参数,让我们升级代码:dosomething函数来接受一个参数,控制他睡眠等待的时间defdosomething(num):print(f线程{num}启动,睡眠{num}秒)time。sleep(num)print(f线程{num}结束)
  在threading中,创建线程时可以使用args来传递参数,例如现在接收一个参数,则上一小节的代码可以如下修改importtimeimportthreadingstarttime。perfcounter()defdosomething(num):print(f线程{num}启动,睡眠{num}秒)time。sleep(num)print(f线程{num}结束)thread1threading。Thread(targetdosomething,args〔1〕)thread2threading。Thread(targetdosomething,args〔2〕)thread1。start()thread2。start()thread1。join()thread2。join()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  这段代码中,我分别让两个线程等待1、2秒,运行结果显然应该是2秒线程1启动,睡眠1秒线程2启动,睡眠2秒线程1结束线程2结束全部任务执行完成,耗时2。01秒
  如果你的线程函数需要更多的参数,只需要依次向args中追加即可。简化代码
  上面的案例中,我们仅开启了两个线程,如果是更多个线程的话,再依次重复定义、启动就会显得十分繁琐,此时我们可以使用循环来处理。
  例如开启10个线程,依次睡眠110秒,可以先创建一个list用于存储每个线程,接着利用循环依次创建线程,启动后追加到刚刚创建的list中,之后再依次等待每个线程执行完毕,代码如下importtimeimportthreadingstarttime。perfcounter()defdosomething(num):print(f线程{num}启动,睡眠{num}秒)time。sleep(num)print(f线程{num}结束)threadlist〔〕foriinrange(1,11):threadthreading。Thread(targetdosomething,args〔i〕)thread。start()threadlist。append(thread)fortinthreadlist:t。join()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  结果是显然的,虽然我们执行了十次dosomething,每次用时110秒,但总耗时应该为10秒线程1启动,睡眠1秒线程2启动,睡眠2秒线程3启动,睡眠3秒线程4启动,睡眠4秒线程5启动,睡眠5秒线程6启动,睡眠6秒线程7启动,睡眠7秒线程8启动,睡眠8秒线程9启动,睡眠9秒线程10启动,睡眠10秒线程1结束线程2结束线程3结束线程4结束线程5结束线程6结束线程7结束线程8结束线程9结束线程10结束全部任务执行完成,耗时10。01秒共享变量锁的问题
  现在,你应该已经了解threading最基本的用法,只需要将dosomthing函数进行修改即可,但是如果你深入使用,还会有其他的问题出现,例如共享变量的问题,让我们继续探讨。
  多线程很常见的一个应用就是爬虫,回到开头的爬虫问题,如果我们希望爬取10个网页的评论,可能会先定一个空dataframe,然后使用多线程都往这个dataframe中写入数据,但由于多个线程同时操作这一个变量,可能会导致评论并不是按照顺序写入的。
  例如第一个页面有10条评论,第一个线程写入了2条后,第二个线程将第二个页面的前两条写入,最终导致十个页面的评论是乱序存储!
  让我们把这个问题抽象出来,还是之前的代码,稍微修改一下
  我们先定义了一个空list,线程函数会将传入的数字添加到该list中,在未加锁的情况下,由于线程竞争,虽然我们线程是按照顺序开启,但是最终数字并不是按照顺序写入。
  有没有办法解决呢?当然有,很自然的想法就是当第一个线程操作该变量时,其他线程等着,写完了再释放,这就是锁!
  先看代码
  在上面的代码中,我们使用threding。Lock创建了一个线程锁,之后在线程函数操作result前,首先使用lock。acquire()加上锁,之后操作results,在修改完后使用lock。relese()释放,此时其他线程若想操作results则会阻塞,等该线程释放后才能拿走操作中,这样我们就保证了线程是安全的!
  最基本的线程锁用法就如上面代码所示,定义锁上锁解锁,但是一定要注意,lock。acquire()和lock。relese(),如果加了锁但是没有释放,后面的线程将会全部阻塞!限制线程数量
  最后还有一个常见的问题,上面我们需要执行几次线程函数就开了几个线程,但是如果需要爬成千上万个网页,开这么多线程cpu一定不同意,代码也会在开启的线程达到一定数量后报错。
  所以如何让程序只启动我们指定的线程数量,例如一次开五个线程,结束一个再添加一个,直到全部任务完成?
  还是锁!在threading模块中有一个BoundedSemaphore(信号量)类,我们可以给他一个初始的信号量(最大线程数),之后每次有线程获得信号量的时候(即acquire())计数器1,释放信号量时候(release())计数器1,计数器为0的时候其它线程就被阻塞无法获得信号量。当计数器为设定好的上限的时候BoundedSemaphore就无法进行release()操作了。
  体现到代码上则比较简单,还是基于上面的例子修改
  总共需要运行十次,我们定义最大线程数为3,并在线程启动前调用acquire方法增加一个计数,在线程最后释放。
  此时程序一次只能启动三个线程,如图中所示,首先启动123,之后完成123,启动456,当第四个线程结束启动第七个线程直到全部线程结束。
  这里我们同时使用了上一节说的线程锁来保护变量,用BoundedSemaphore锁来控制最大线程数,在实际写代码时就需要小心检查锁是否正确释放,否则就会报错!一个真实的多线程爬虫案例
  至此,threading模块最常见的用法就介绍完毕,现在让我们回到本文一开始的问题,有多个(以十个为例)URL需要爬取,既然每个页面需要执行的操作一样,如果等待一个页面爬取完毕再爬第二页面就太浪费时间了。这时就可以仿照上面的思路去使用多线程加速。
  我们只需要将上面的dosomething函数修改为对也面的爬取操作,之后的创建启动线程操作不变即可,代码如下importtimeimportthreadingimportrequestsimportpandasaspdfromfakerimportFakerfrombs4importBeautifulSoupdefcrawurl(url):globaldffakeFaker()headers{UserAgent:fake。useragent()}rrequests。get(url,headersheaders)soupBeautifulSoup(r。content,html。parser)reviewlistsoup。findall(classmainreviewitem)foriinrange(len(reviewlist)):rankreviewlist〔i〕。select(span)〔0〕。get(title)time1reviewlist〔i〕。select(span)〔1〕。get(content)titlereviewlist〔i〕。select(h2a)〔0〕。textdfdf。append({时间:time1,评分:rank,标题:title,},ignoreindexTrue)print(爬取完成)ifnamemain:starttime。perfcounter()dfpd。DataFrame(columns〔时间,评分,标题〕)urllist〔https:movie。douban。comsubject1652587reviews?sorttimestart0,https:movie。douban。comsubject1652587reviews?sorttimestart20,https:movie。douban。comsubject1652587reviews?sorttimestart40,https:movie。douban。comsubject1652587reviews?sorttimestart60,https:movie。douban。comsubject1652587reviews?sorttimestart80,https:movie。douban。comsubject1652587reviews?sorttimestart100,https:movie。douban。comsubject1652587reviews?sorttimestart120,https:movie。douban。comsubject1652587reviews?sorttimestart140,https:movie。douban。comsubject1652587reviews?sorttimestart160,https:movie。douban。comsubject1652587reviews?sorttimestart180〕threadlist〔〕foriinurllist:threadthreading。Thread(targetcrawurl,args〔i〕)thread。start()threadlist。append(thread)fortinthreadlist:t。join()finishtime。perfcounter()print(f全部任务执行完成,耗时{round(finishstart,2)}秒)
  执行这段代码,差不多仅用了1秒就将全部内容爬取并存储到dataframe中,比同步的代码块了近十倍!如果感兴趣的话可以自己尝试一下。
  至此,有关Python多线程模块threading的基本用法与需要注意的几点就介绍完毕,如果全部认真看完的话,我相信你一定能照猫画虎写出第一个多线程爬虫脚本。
  当然有关Python多线程还有很多饱受诟病的争议(GIL),多线程的实现方法也远不止threading模块,例如更常见的写法是通过concurrent。futures模块以及多进程、协程,这些都留在本系列后续文章中再进一步讨论!
投诉 评论 转载

美团公开新专利指纹解锁共享单车IT之家1月12日消息,近日,美团关联公司北京三快在线科技有限公司公开车辆解锁方法、装置、共享车辆及存储介质专利,公开号CN113920618A。企查查专利摘要显示,该专……一日回顾丨华为P505G有戏吗?苹果端水大师多条腿走路7月29日,回顾昨日科技资讯,科技数码圈儿有什么值得关注的呢?华为新品发布会即将上线,今天有博主旺仔百事通放出一份线下演示机SKU名单,显示P50Pro仅有麒麟90004……工信部装备中心与华为签约,聚焦智能网联汽车产业顶层设计电车汇消息:9月25日,在2021世界智能网联汽车大会期间,工业和信息化部装备工业发展中心(简称装备中心)与华为技术有限公司(简称华为)举行战略合作签约仪式。工业和信息化部副部……小米第二,韬光养晦为上策雷军放下话来,三年内世界第一。草率了,直接把自身置于标靶地境。首先,三不三恶仆欧就会多了些想法。他们的功力,可是从上世纪八十年代就在IT界积攒起来的。也许,雷布斯的开始汇……XR云创作平台Echo3D获400万美元融资HTC或将发布全(VRPinea10月8日讯)今日重点新闻:美国XR云创作平台Echo3D已完成400万美元融资,本轮融资由KonvoyVentures领投;HTC将于10月14日举办在线Vi……微信又出新功能,家长速看微信青少年模式保护功能再次升级了前往我设置青少年模式开启后进入视频号将能看到精选的适合青少年的内容该功能目前正在覆盖中无论是我关注的青……骨架作为温室大棚的灵魂,如何不被忽悠,需要了解以下几点温室骨架作为大棚的脊梁,它的安全性决定大棚的使用期限,而实际当中往往会选择价格低,而不是去看它的品质如何?也就造成如今的画面,遇雪棚倒事件。最近东北的很多大棚被大雪压塌了,也许……人人都能学会的Python多线程指南来源:早起Python作者:刘早起在Python中,多线程最常见的一个场景就是爬虫,例如这样一个需求,有多个结构一样的页面需要爬取,例如下方的URL(豆瓣阿凡达影评……iPhone苹果更新14。7后,手机待机都有点热,刷头条抖音iso14。7正式版iPhoneiPhone6sp更新14。7后待机都是热的,刷头条看文章都是烫的。刷抖音别说了,十分钟收指都是痛的,把手机放到手背上感觉是在烤火,手机掉电太快……李在镕访问美国,确定芯片厂最终选址,三星玩命追赶台积电电子发烧友网报道(文李弯弯)据韩联社报道,李在镕海外访问结束,并确定了美国新芯片工厂选址,将在本周对外公布。近年以来美国表示要大力发展芯片制造,并相继出台优惠政策吸引和鼓励海内……唯科模塑创新引领高质量发展模具通常是指在工业生产中通过注塑、吹塑、挤出、压铸或锻压成型、冶炼、冲压等工艺得到所需产品的各种工具,模具技术是衡量一个国家产品制造水平的重要指标,厦门唯科模塑科技股份有限公司……游戏改编电影?不行!游戏公司做电影?行由同名游戏改编的《真三国无双》电影没有意外的扑街了,但谁也没想到会扑的这么彻底。在5月1日上映后,一整个五一黄金周的时间,票房总收入1400万元,上映6天后就从院线撤档上……
国产新能源卖不动,但特斯拉新车却火了,是技术问题还是品牌问题手机通话音量太小,快打开这个开关北一机床重型数控龙门车铣复合机床再创新纪录碳纤维那么优秀为何笔记本主流还是金属材质?不想买新能源,这些车搭载48V轻混,开起来照样很巴适直面iPhone13小米11T发布会明天举行油皮真爱!8款夏季巨持久又轻薄的粉底液大揭秘商用洗碗机的分类和特点科技与历史碰撞海信TOUCH西安博物院联名礼盒开启预售年轻人的首辆智能轿跑思皓曜开启预售后悔购买集成灶?那是你没挑对品牌399元起!诺基亚两款手机支持支付宝了
咸也好淡也好天骄造句用天骄造句大全寻衅滋事罪主事回来了那从犯还会受到处罚吗?胖不起来,瘦不下去!小心肠漏症人生短短数十载,在有限的时间内,及时行乐南柯子、秋池无边茶马古道新宋词诗书画热评聚热点网 赵构算是中兴之主吗究竟该如何评价赵构冬天慢跑的好处有哪些女人性高潮会有什么表现让我们携手保护树木作文400字高中生关于细节的作文素材抒情散文你是我诗中最美的句子

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找美丽时装彩妆资讯历史明星乐活安卓数码常识驾车健康苹果问答网络发型电视车载室内电影游戏科学音乐整形