这是菜鸟学Python的第62篇原创文章
阅读本文大概需要6分钟
Python的并发编程前面只讲了进程和线程的入门篇,线程里面还有很多有用而好玩的招式,我们还没有讲,我一直主张学一门语言一定要有兴趣,带着玩Python的心态去学,会轻松很多,打个比方打扫卫生和打羽毛球,可能都很累,为啥你打球就很high,而在家打扫卫生就很累,因为你enjoy打球哈哈,好言归正传,今天我们就来讲讲多线程编程~~
- 线程的同步
- 线程的锁
- 线程的通讯
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()对象来设置信号量,通过这个信号量来进行两个子线程的同步
- 在多线程中,由于所有的变量都由所有线程共享,所以在运行的过程中,任何一个变量都可以被任何一个线程修改
- 因此,线程之间共享数据会有risk,很可能跑着跑着,数据就被别人改了,所以我们需要加锁来控制
- 举一个非常经典的例子,就是银行存取款的,相信很多同学都听说过这个例子,在传统的c++,java里面也大量用这个例子来说明锁的重要性,我们来看一下
1).假设银行存款有1000元,有两个线程对这笔钱进行操作
我再存1块钱,然后再取1块钱
我再存10块钱,然后再取10块钱
理论上讲无论多少次,这笔钱应该还是1000块,但是当两个线程同事进行大量频繁的操作的时候,诶情况就有变化了,钱多了,或者少了,不信你看:
>>
1097
[Finished in 0.3s]
哇多了97块钱,真是好事啊,哈哈,银行肯定不乐意了,要找原因了
11).我们在算钱的时候,做了两步
第一步:balance=balance+n
第二步:balance=balance-n
12).底层操作系统在执行的时候会分成好几步去执行,python是有C写的,C又会变成汇编语言去执行指令,所以当两个线程频繁的操作的时候,中间的指令会被打断,从而导致多个线程把同一个变量的内容改乱了(当然我们是希望钱变多哈哈)
2).所以一般对于多线程处理核心的数据,都是要加锁的
就是让某一个时刻,只能有一个线程改这个变量,让这个线程执行完了,释放锁,再让下一个线程执行,保证核心数据不会被改乱了~~比如我们执行10,100万次存取,都不会出差错
>>
1000
[Finished in 1.1s]
创建一个threading.Lock()对象,在处理核心数据操作的时候,先锁起来,用完再释放锁,看现在反复操作100000次,钱还是1000,一分没有少,大家有没有发现一个问题,这个是有代价的,就是运行开销很大,原来是0.3s运行完,现在要1.1s才能搞定
好比我有一个工作要做,一个线程要干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来通知线程终止的.
好了Python中的多线程-进阶篇就讲到这里啦,是不是觉得蛮好玩的,希望能给初学者一些启发,若有什么不懂的,也可以留言跟我探讨交流.
Python语言如何入门 最全的零基础学Python的问题,你想知道的都在这里 Python入门原创文章,2016年度大盘点 用Python写个弹球的游戏 Python写个迷你聊天机器人|生成器的高级用法 用Python破解微软面试题|24点游戏 一道Google的算法题 |Python巧妙破解
本文为原创文章,版权归知行编程网所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 如何在 python 中使用 random.randint 函数?09/05
- ♥ python3教程递归反向输出字符串11/08
- ♥ 如何在 Python 中使用 ord() 函数?09/02
- ♥ python标准库模块放在哪里?09/22
- ♥ 什么是python复杂08/18
- ♥ 100 个 pandas 数据分析函数总结06/23
内容反馈