大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说Python 线程进阶篇 | 多线程下载网页[亲测有效],希望您对编程的造诣更进一步.
Python的并发编程前面只讲了进程和线程的入门篇,线程里面还有很多有用而好玩的招式,我们还没有讲,我一直主张学一门语言一定要有兴趣,带着玩Python的心态去学,会轻松很多,打个比方打扫卫生和打羽毛球,可能都很累,为啥你打球就很high,而在家打扫卫生就很累,因为你enjoy打球哈哈,好言归正传,今天我们就来讲讲多线程编程~~
要点:
线程的同步
线程的锁
线程的通讯
1.线程如何同步
1).若主程序创建了一个线程之后,启动线程start().线程的一个关键特性是每个线程都是独立运行且状态不可预测
我们希望线程启动运行后,线程内部运行到某种情况的时候,主程序才执行
比如程序中的其他线程需 要通过判断某个线程的状态来确定自己下一步的操作
这时线程同步问题就会变得非常棘手,怎么破,很简单,利用线程threading.Event(),来设置信号量来控制
>>
Start thread1…
1
2
3
Start to run in main function
4
5
6
主程序创建一个Event()对象my_event,把my_event传到子线程里面
在子线程里面设置my_event.set()也就是说子线程运行到i==4时把信号量置为True而外边的主函数my_event.wait()一直阻塞在那里,傻傻的等着信号量的状态
一旦set()了意味着信号量为True了,外边主函数就可以开始行动了
2).同样我们假设有2个子线程,那两个子线程之间的同步也可以用Event来搞定
>>
Start thread1…
1
2
3
4
Ok,we can start thread2…
5
6
线程thread1和线程wait_for_thread1通过Event()对象来设置信号量,通过这个信号量来进行两个子线程的同步
2. 线程的锁
在多线程中,由于所有的变量都由所有线程共享,所以在运行的过程中,任何一个变量都可以被任何一个线程修改
因此,线程之间共享数据会有risk,很可能跑着跑着,数据就被别人改了,所以我们需要加锁来控制
举一个非常经典的例子,就是银行存取款的,相信很多同学都听说过这个例子,在传统的c++,java里面也大量用这个例子来说明锁的重要性,我们来看一下
1).假设银行存款有1000元,有两个线程对这笔钱进行操作
我再存1块钱,然后再取1块钱
我再存10块钱,然后再取10块钱
理论上讲无论多少次,这笔钱应该还是1000块,但是当两个线程同事进行大量频繁的操作的时候,诶情况就有变化了,钱多了,或者少了,不信你看:
>>
1097
[Finished in0.3s]
哇多了97块钱,真是好事啊,哈哈,银行肯定不乐意了,要找原因了
11).我们在算钱的时候,做了两步
第一步:balance=balance+n
第二步:balance=balance-n
12).底层操作系统在执行的时候会分成好几步去执行,python是有C写的,C又会变成汇编语言去执行指令,所以当两个线程频繁的操作的时候,中间的指令会被打断,从而导致多个线程把同一个变量的内容改乱了(当然我们是希望钱变多哈哈)
2).所以一般对于多线程处理核心的数据,都是要加锁的
就是让某一个时刻,只能有一个线程改这个变量,让这个线程执行完了,释放锁,再让下一个线程执行,保证核心数据不会被改乱了~~比如我们执行10,100万次存取,都不会出差错
>>
1000
[Finished in1.1s]
创建一个threading.Lock()对象,在处理核心数据操作的时候,先锁起来,用完再释放锁,看现在反复操作100000次,钱还是1000,一分没有少,大家有没有发现一个问题,这个是有代价的,就是运行开销很大,原来是0.3s运行完,现在要1.1s才能搞定
3. 线程之间的通信
好比我有一个工作要做,一个线程要干6分钟,那么我创建3个线程,同时去处理这些事情,是不是只要2分钟.效率高了许多.
那么怎么样才能让这些线程之间实现安全的通信或者交换数据呢
Python里面有一个高效安全的Queue模块,我们主需要把数据塞进队列,它就会被所有的线程共享
然后用put()或者get()操作来给队列添加或者移除元素
我们举一个多线程爬网页的例子吧
1).首先创建一个DownloadThread类,这个类继承threading.Thread
2).类的初始化函数,留一个接口,把queue带进去,这样所有的线程都可以共享这个queue
3).类的run函数,设计成死循环,不断的从队列里面取数据,一旦这个线程把从这个队列里面的数据取完了,就调用queue.task_done()表示我已经干完了
4).类的download_file函数,其实就是具体去执行下载网页的工作,因为爬虫后面再讲,所以我这边只用了很简单的urllib.urlopen()函数,简单获取网页而已
接着看main函数
>>
Thread-3:begin dowbload http://wiki.python.org/moin/WebProgramming…
Thread-1:begin dowbload https://wiki.python.org/moin/WebFrameworks…
Thread-2:begin dowbload https://wiki.python.org/moin/BeginnersGuide…
Thread-2 download completed
Thread-2:begin dowbload https://wiki.python.org/moin/PythonBooks…
Thread-1 download completed
Thread-1:begin dowbload https://wiki.python.org/moin/PythonEvents…
Thread-2 download completed
Thread-1 download completed
Thread-3 download completed
Cost time:0:00:14.604000
1).main函数里面我们设定了有5个网页要爬
2).启动3个线程,并且设定为守护线程,就是当main()函数一结束,所有的子线程全部结束
3),把5个网页(也可以认为是5个要完成的task),这里是要爬网页,以后你可以变成其他数据结构,或者直接封装成类然后塞进queue里面,让这3个线程去共享完成.
4).queue会阻塞在哪里,等到所以的线程完成那一刻
5).大家看Thread2完成了之后,又会接着去爬下一个网页(真是个勤快的孩子),Thread1也是完成之后接着去爬下一个,看起来Thread3执行的网页爬的最慢
6).最后计算一下时间,大家改一下代码,可以比较一下一个线程爬5个网页和3个线程同时去快多少(大概是3倍)
补充说一下,这个爬网页urlopen()没有加任何异常保护,当然在实际操作中是肯定要保护,并且当出现异常的时候要设置一个flag来通知线程终止的.
需要源码后台留言
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13544.html