范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

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

  来源:早起Python
  作者:刘早起
  在  Python  中,多线程最常见的一个场景就是爬虫,例如这样一个需求,有多个结构一样的页面需要爬取,例如下方的URL(豆瓣阿凡达影评,以10个为例)  url_list = [       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=0",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=20",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=40",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=60",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=80",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=100",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=120",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=140",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=160",       "https://movie.douban.com/subject/1652587/reviews?sort=time&start=180"]
  如果依次爬取, 请求第一个页面——得到返回数据——解析数据——提取、存储数据——请求第二个页面 ,按照这样的思路,那么 大量时间都会浪费在请求、返回数据 上,如果在等待第一个页面返回数据时去请求第二个页面,就能有效的提高效率,多线程就可以实现这样的功能。
  在Python中实现多线程的方法也很多,我将基于  threading  模块一点一点介绍,注意本文不会太注重于多线程背后的技术概念(面试常问),仅希望用最少的话教会大家如何实现。当然会在最后介绍如何使用threading 模块来解决上面的爬虫问题。threading基本使用
  让我们先从一个简单的例子开始,定义 do_something 函数,执行该函数需要消耗1秒import time  start = time.perf_counter()  def do_something():     print("-> 线程启动")     time.sleep(1)     print("-> 线程结束")  do_something()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  上面的代码不难理解,执行 do_something 并计算耗时,结果很明显应该是1s-> 线程启动 -> 线程结束 全部任务执行完成,耗时 1.01 秒
  现在如果需要 执行两次 do_something ,按照最基本的思路import time  start = time.perf_counter()   def do_something():     print("-> 线程启动")     time.sleep(1)     print("-> 线程结束")   do_something() do_something()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  执行上面代码结果也很容易猜到是2秒 -> 线程启动 -> 线程结束 -> 线程启动 -> 线程结束 全部任务执行完成,耗时 2.01 秒
  这就是最常规的  同步  思路,在CPU执行第一个函数,也就是等待1s的时间内,什么也不干, 等第一个函数执行完毕后再执行第二个函数
  很明显,这样让CPU干等着啥也不干并不是一个很好的选择,而 多线程 就是解决这一问题的方法之一, 让CPU在等待某个任务完成时去执行更多的操作 ,将整个过程简化为下图流程,这样就能充分节省时间
  现在使用 threading 来通过多线程的方式实现上面的过程,非常简单,定义两个线程并依次启动即可import time import threading  start = time.perf_counter()   def do_something():     print("-> 线程启动")     time.sleep(1)     print("-> 线程结束")   thread1 = threading.Thread(target=do_something) thread2 = threading.Thread(target=do_something)  thread1.start() thread2.start()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  执行上面的代码,结果如下 -> 线程启动 -> 线程启动 全部任务执行完成,耗时 0.0 秒 -> 线程结束 -> 线程结束
  可以看到, 两个子线程确实同时启动,但是主线程并未等待两个子线程执行完毕就直接结束 。
  为了解决这个问题,我们可以使用 threading.join() 方法,意思是在子线程完成运行之前,这个子线程的父线程将一直被阻塞
  换成人话就是 让主线程挂起,等待所有子线程结束再执行 ,体现到代码上也很简单,只需要添加两行即可 import time import threading  start = time.perf_counter()   def do_something():     print("-> 线程启动")     time.sleep(1)     print("-> 线程结束")   thread1 = threading.Thread(target=do_something) thread2 = threading.Thread(target=do_something)  thread1.start()  thread2.start()  thread1.join() thread2.join()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  运行结果如下,全部代码在1秒内运行完毕 -> 线程启动 -> 线程启动 -> 线程结束 -> 线程结束 全部任务执行完成,耗时 1.01 秒
  至此,我们就得到了第一个有效的多线程代码,相信你也能大致明白 threading 的基本使用流程。传递参数
  现在来看看如何在多线程之间传递参数,让我们升级代码: do_something 函数来接受一个参数,控制他睡眠等待的时间def do_something(num):     print(f"-> 线程{num} 启动,睡眠 {num} 秒")     time.sleep(num)     print(f"-> 线程{num} 结束")
  在  threading  中,创建线程时可以使用 args  来传递参数,例如现在接收一个参数,则上一小节的代码可以如下修改import time import threading  start = time.perf_counter()  def do_something(num):     print(f"-> 线程{num} 启动,睡眠 {num} 秒")     time.sleep(num)     print(f"-> 线程{num} 结束")  thread1 = threading.Thread(target=do_something,args = [1]) thread2 = threading.Thread(target=do_something,args = [2])  thread1.start() thread2.start()  thread1.join() thread2.join()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  这段代码中,我分别让两个线程等待1、2秒,运行结果显然应该是2秒 -> 线程1 启动,睡眠 1 秒 -> 线程2 启动,睡眠 2 秒 -> 线程1 结束 -> 线程2 结束 全部任务执行完成,耗时 2.01 秒
  如果你的线程函数需要更多的参数,只需要依次向args中追加即可。 简化代码
  上面的案例中,我们仅开启了两个线程, 如果是更多个线程的话,再依次重复定义、启动就会显得十分繁琐 ,此时我们可以使用 循环 来处理。
  例如开启10个线程,依次睡眠1-10秒,可以先创建一个  list  用于存储每个线程,接着利用循环依次创建线程,启动后追加到刚刚创建的 list  中,之后再依次等待每个线程执行完毕,代码如下import time import threading  start = time.perf_counter()  def do_something(num):     print(f"-> 线程{num} 启动,睡眠 {num} 秒")     time.sleep(num)     print(f"-> 线程{num} 结束")  thread_list = []  for i in range(1,11):      thread = threading.Thread(target=do_something, args=[i])     thread.start()     thread_list.append(thread)  for t in thread_list:      t.join()  finish = time.perf_counter()  print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  结果是显然的,虽然我们执行了十次 do_something ,每次用时1-10秒,但总耗时应该为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  最基本的用法,只需要将 do_somthing  函数进行修改即可,但是如果你深入使用,还会有其他的问题出现,例如共享变量的问题,让我们继续探讨。
  多线程很常见的一个应用就是爬虫,回到开头的爬虫问题,如果我们希望爬取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需要爬取,既然每个页面需要执行的操作一样,如果等待一个页面爬取完毕再爬第二页面就太浪费时间了。这时就可以仿照上面的思路去使用多线程加速。
  我们只需要将上面的 do_something 函数修改为对也面的爬取操作,之后的创建启动线程操作不变即可,代码如下import time import threading import requests import pandas as pd from faker import Faker from bs4 import BeautifulSoup   def craw_url(url):     global df     fake = Faker()     headers = {"User-Agent": fake.user_agent()}     r = requests.get(url, headers=headers)     soup = BeautifulSoup(r.content, "html.parser")     review_list = soup.find_all(class_="main review-item")      for i in range(len(review_list)):          rank = review_list[i].select("span")[0].get("title")         time1 = review_list[i].select("span")[1].get("content")         title = review_list[i].select("h2>a")[0].text         df = df.append({"时间": time1,                         "评分": rank,                         "标题": title, }, ignore_index=True)      print("-> 爬取完成")   if __name__ == "__main__":      start = time.perf_counter()     df = pd.DataFrame(columns=["时间", "评分", "标题"])      url_list = [         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=0",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=20",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=40",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=60",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=80",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=100",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=120",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=140",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=160",         "https://movie.douban.com/subject/1652587/reviews?sort=time&start=180"]     thread_list = []     for i in url_list:          thread = threading.Thread(target=craw_url, args=[i])         thread.start()         thread_list.append(thread)      for t in thread_list:          t.join()      finish = time.perf_counter()          print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
  执行这段代码,差不多仅用了1秒就将全部内容爬取并存储到  dataframe  中,比同步的代码块了近十倍!如果感兴趣的话可以自己尝试一下。
  至此,有关 Python 多线程模块  threading  的基本用法与需要注意的几点就介绍完毕,如果全部认真看完的话,我相信你一定能照猫画虎写出第一个多线程爬虫脚本。
  当然有关 Python 多线程还有很多饱受诟病的争议(GIL),多线程的实现方法也远不止  threading  模块,例如更常见的写法是通过 concurrent.futures  模块以及多进程、协程,这些都留在本系列后续文章中再进一步讨论!

一个比特币卖30万,家用电脑24小时挖矿,多久能挖到一枚?比特币很值钱。202103130500的汇率显示1比特币兑换368182。90元人民币。一先了解一下比特币。比特币是一种数字货币。可能很多人不是很了解虚拟货币。虚拟货币只能在数字世说说你手机里最常用的五个app是什么?谢邀,我这段时间用的最频繁的要数扶贫app,占的比例也比较大,如贵州扶贫云,社会扶贫,钉钉,我县的就业云。交流平台,跟头条有关的,如头条(信息创造价值版)今日头条(极速版),趣头条私有制是文明起源吗?私有制是万恶之源,是剥削人民的开始,是又倒退到了资本家剥削压迫人民的旧社会,私有制是文明的死敌,文明来源于集体主义,社会主义,共产主义!私有制是文明的退化,中国从古至今是从奴隶社会华为手机升级HarmonyOS2。0系统办法题主手机华为mate30丐版升级HarmonyOS2。0如下操作点击我的华为进去有升级尝鲜选项点进去看到自己目前支持的机型去立即尝鲜找到自己的机型报名公测下拉底部有参加公测报名注意华为鸿蒙过分随意,比安卓和苹果更烧脑,万物归一要靠消费者鸿蒙HarmonyOS2。06月2日晚8点,华为消费者业务CEO余承东,携手软件部总裁王成录博士,揭开鸿蒙HarmonyOS2。0的神秘面纱。此前,鸿蒙仅搭载于智能电视等产品如今,华为曝光P50手机摄像头模组定了在昨晚的华为HarmonyOS2发布会中,发布会尾声余承东给出了一个彩蛋,那就是P50系列手机的谍照正式被华为官宣,余承东也坦言称华为P系列旗舰按惯例在每年春季更新,但是P50系列小米手机将上线地震监测功能,利用传感器AI算法进行感应IT之家6月3日消息小米手机此前宣布,地震预警功能已经支持全国25个省份(含直辖市自治区)的大部分地区。小米称,自地震预警功能上线以来,成功预警4。0级以上地震35次以上。今日小米苹果芯片爆漏洞,果粉脑洞大开一定是华为买的热搜作为全球唯一能将芯片和系统这两项核心技术都做到世界顶尖水平的科技巨头,苹果每年发布新产品都会受到很大的关注,稍有风吹草动,就会引起同行业消费者,甚至吃瓜群众的关注。科技公司出现漏洞5款手机APP功能实用,满满的黑科技今天和大家分享5款手机APP,功能实用满满的黑科技,一起来看看吧!一个木函可以说拥有了它一个等于拥有了几十个app!整个画面简练精巧,体积也很小!不占地方,又好用!点开看看功能!可iPhone13可能会加两款新配色并成为苹果主打的网红款从目前了解到的消息来看,苹果iPhone13外型上应该大致跟iPhone12一样,但手机正面的刘海部份将会减小。而要让大家知道是全新机型,推出新配色或是最直接的方法,近日就有消息称鸿蒙出世,但华为大考刚开始,需要直面两场战争鸿蒙出世,是上亿级的装机体量,也是重量级的残酷大考。6月2日晚间,随着蛰伏数年的全场景分布式操作系统鸿蒙HarmonyOS2浮出水面,华为在多终端多设备连接上的布局链条逐渐清晰。当
数据结构与算法基础(十八)哈希表摘要哈希表整体结构就是一个数组,元素的结构是keyvalue形式,因为key可以是任意可能类型,那么就需要一个标准性的生成唯一索引方式,所以就引出哈希值这个名称。本文在介绍哈希表时虎年首虎李国华落马,曾在邮政系统任职超30年2月18日晚间,中国联合网络通信集团有限公司原党组副书记总经理李国华落马。这距离其退休不到两年时间。李国华(资料图)时间回到近两年前的2020年3月12日,当天,李国华退休,中国联元宇宙一场全新的商业机会版图本报记者屈丽丽编者按毫无疑问,新冠肺炎疫情的出现正导致两个后果一是人们在物理世界见面很少,二是数字世界的联系更加紧密。在这一背景之下,人们需要一个更好的数字化空间来承载数字化的生活别再说啫喱是元宇宙社交了,它就是潮玩版微信我,一个90后老阿姨,在啫喱app玩了整整一周了。最火的时候,啫喱登上AppStore中国区免费榜榜首,短暂地把微信挤成老二。但风光没几天,又因为人偶穿模卡出代码服装侵权用户隐私等照片打印机也可这样玩?照片视频跃然纸上,汉印CP4000L体验提到了照片打印机,年轻一族一定不会陌生。也许大家会说,电子照片保存都这么方便了,入手照片打印是否多此一举呢?其实不然,很多的时候,真实的照片更代表着美好的回忆,每当家人聚在一起翻看蚊子最喜欢的颜色为了避免黄热病,尽量不要穿黄色衣服。根据一项新的研究,一些颜色可能对携带致命疾病的埃及伊蚊更有吸引力。华盛顿大学研究人员的发现加深了我们对这种害虫行为的了解,并提供了更多方法来避免网传一段特斯拉失控视频直接飞跨过河气囊全爆,司机被送医院今日,有网友上传了一段极其夸张的特斯拉车祸视频。从救护车可知,事发地疑似河南开封,视频中,这辆特斯拉的前部和尾部,都有明显的车损,并且气囊全爆,车内的驾乘人员被救护车接走。让人难以为什么不建议你用去!null做判空?为了避免空指针调用,我们经常会看到这样的语句if(someobject!null)someobject。doCalc()最终,项目中会存在大量判空代码,丑陋繁杂如何避免这种情况?是向日葵C1Pro智能插座体验普通插座售价,也能实现远程开机协作现实生活中,我们常常遇到这样的情况,有时下班回家了,突然有一份文件要处理,明明可以远程处理,或者发送回家处理,但是因为公司电脑关闭了,只好又花费一两个小时跑回公司,浪费了不少时间和你用过几款手机?手机升级记2007年高三,拥有人生中的第一部手机,是叔叔送我的夏新那年超级女声很火,李宇春代言了夏新手机。当时最火的游戏是玩泡泡龙。同学们都借着手机去玩游戏,那时候手机还不普及,只节后开工开学装备首选,游戏性能轻薄本灵耀Pro16让新的一年更有劲儿不知不觉虎年春节后的第一个重要节日元宵节已过,一元复始大地回春我们也将进入2022年正式的工作与学习中。迎接新一年的挑战想要提升一下装备,笔者为大家推荐华硕灵耀Pro16,目前华硕