深入理解 Python 中的多线程 新手必看


Posted in Python onNovember 20, 2016

示例1
我们将要请求五个不同的url:
单线程

import time
import urllib2
 
defget_responses():
  urls=[
    ‘http://www.baidu.com',
    ‘http://www.amazon.com',
    ‘http://www.ebay.com',
    ‘http://www.alibaba.com',
    ‘https://3water.com'
  ]
  start=time.time()
  forurlinurls:
    printurl
    resp=urllib2.urlopen(url)
    printresp.getcode()
  print”Elapsed time: %s”%(time.time()-start)
 
get_responses()

输出是:
http://www.baidu.com200
http://www.amazon.com200
http://www.ebay.com200
http://www.alibaba.com200
https://3water.com200
Elapsed time:3.0814409256

解释:
url顺序的被请求
除非cpu从一个url获得了回应,否则不会去请求下一个url
网络请求会花费较长的时间,所以cpu在等待网络请求的返回时间内一直处于闲置状态。
多线程

import urllib2
import time
from threading import Thread
 
classGetUrlThread(Thread):
  def__init__(self, url):
    self.url=url
    super(GetUrlThread,self).__init__()
 
  defrun(self):
    resp=urllib2.urlopen(self.url)
    printself.url, resp.getcode()
 
defget_responses():
  urls=[
    ‘http://www.baidu.com',
    ‘http://www.amazon.com',
    ‘http://www.ebay.com',
    ‘http://www.alibaba.com',
    ‘https://3water.com'
  ]
  start=time.time()
  threads=[]
  forurlinurls:
    t=GetUrlThread(url)
    threads.append(t)
    t.start()
  fortinthreads:
    t.join()
  print”Elapsed time: %s”%(time.time()-start)
 
get_responses()

输出:
https://3water.com200
http://www.baidu.com200
http://www.amazon.com200
http://www.alibaba.com200
http://www.ebay.com200
Elapsed time:0.689890861511

解释:

意识到了程序在执行时间上的提升
我们写了一个多线程程序来减少cpu的等待时间,当我们在等待一个线程内的网络请求返回时,这时cpu可以切换到其他线程去进行其他线程内的网络请求。
我们期望一个线程处理一个url,所以实例化线程类的时候我们传了一个url。
线程运行意味着执行类里的run()方法。
无论如何我们想每个线程必须执行run()。
为每个url创建一个线程并且调用start()方法,这告诉了cpu可以执行线程中的run()方法了。
我们希望所有的线程执行完毕的时候再计算花费的时间,所以调用了join()方法。
join()可以通知主线程等待这个线程结束后,才可以执行下一条指令。
每个线程我们都调用了join()方法,所以我们是在所有线程执行完毕后计算的运行时间。

关于线程:

cpu可能不会在调用start()后马上执行run()方法。
你不能确定run()在不同线程建间的执行顺序。
对于单独的一个线程,可以保证run()方法里的语句是按照顺序执行的。
这就是因为线程内的url会首先被请求,然后打印出返回的结果。

实例2

我们将会用一个程序演示一下多线程间的资源竞争,并修复这个问题。

from threading import Thread
 
#define a global variable
some_var=0
 
classIncrementThread(Thread):
  defrun(self):
    #we want to read a global variable
    #and then increment it
    globalsome_var
    read_value=some_var
    print”some_var in %s is %d”%(self.name, read_value)
    some_var=read_value+1
    print”some_var in %s after increment is %d”%(self.name, some_var)
 
defuse_increment_thread():
  threads=[]
  foriinrange(50):
    t=IncrementThread()
    threads.append(t)
    t.start()
  fortinthreads:
    t.join()
  print”After 50 modifications, some_var should have become 50″
  print”After 50 modifications, some_var is %d”%(some_var,)
 
use_increment_thread()

多次运行这个程序,你会看到多种不同的结果。
解释:
有一个全局变量,所有的线程都想修改它。
所有的线程应该在这个全局变量上加 1 。
有50个线程,最后这个数值应该变成50,但是它却没有。
为什么没有达到50?
在some_var是15的时候,线程t1读取了some_var,这个时刻cpu将控制权给了另一个线程t2。
t2线程读到的some_var也是15
t1和t2都把some_var加到16
当时我们期望的是t1 t2两个线程使some_var + 2变成17
在这里就有了资源竞争。
相同的情况也可能发生在其它的线程间,所以出现了最后的结果小于50的情况。
解决资源竞争

from threading import Lock, Thread
lock=Lock()
some_var=0
 
classIncrementThread(Thread):
  defrun(self):
    #we want to read a global variable
    #and then increment it
    globalsome_var
    lock.acquire()
    read_value=some_var
    print”some_var in %s is %d”%(self.name, read_value)
    some_var=read_value+1
    print”some_var in %s after increment is %d”%(self.name, some_var)
    lock.release()
 
defuse_increment_thread():
  threads=[]
  foriinrange(50):
    t=IncrementThread()
    threads.append(t)
    t.start()
  fortinthreads:
    t.join()
  print”After 50 modifications, some_var should have become 50″
  print”After 50 modifications, some_var is %d”%(some_var,)
 
use_increment_thread()

再次运行这个程序,达到了我们预期的结果。
解释:
Lock 用来防止竞争条件
如果在执行一些操作之前,线程t1获得了锁。其他的线程在t1释放Lock之前,不会执行相同的操作
我们想要确定的是一旦线程t1已经读取了some_var,直到t1完成了修改some_var,其他的线程才可以读取some_var
这样读取和修改some_var成了逻辑上的原子操作。
实例3
让我们用一个例子来证明一个线程不能影响其他线程内的变量(非全局变量)。
time.sleep()可以使一个线程挂起,强制线程切换发生。

from threading import Thread
import time
 
classCreateListThread(Thread):
  defrun(self):
    self.entries=[]
    foriinrange(10):
      time.sleep(1)
      self.entries.append(i)
    printself.entries
 
defuse_create_list_thread():
  foriinrange(3):
    t=CreateListThread()
    t.start()
 
use_create_list_thread()

运行几次后发现并没有打印出争取的结果。当一个线程正在打印的时候,cpu切换到了另一个线程,所以产生了不正确的结果。我们需要确保print self.entries是个逻辑上的原子操作,以防打印时被其他线程打断。
我们使用了Lock(),来看下边的例子。

from threading import Thread, Lock
import time
 
lock=Lock()
 
classCreateListThread(Thread):
  defrun(self):
    self.entries=[]
    foriinrange(10):
      time.sleep(1)
      self.entries.append(i)
    lock.acquire()
    printself.entries
    lock.release()
 
defuse_create_list_thread():
  foriinrange(3):
    t=CreateListThread()
    t.start()
 
use_create_list_thread()

这次我们看到了正确的结果。证明了一个线程不可以修改其他线程内部的变量(非全局变量)。

Python 相关文章推荐
Windows上配置Emacs来开发Python及用Python扩展Emacs
Nov 20 Python
Python连接SQLServer2000的方法详解
Apr 19 Python
django使用xlwt导出excel文件实例代码
Feb 06 Python
Python实现的多项式拟合功能示例【基于matplotlib】
May 15 Python
利用Python读取txt文档的方法讲解
Jun 23 Python
python处理大日志文件
Jul 23 Python
Python django搭建layui提交表单,表格,图标的实例
Nov 18 Python
Python 从attribute到property详解
Mar 05 Python
Windows下Anaconda安装、换源与更新的方法
Apr 17 Python
Python 字节流,字符串,十六进制相互转换实例(binascii,bytes)
May 11 Python
浅谈keras通过model.fit_generator训练模型(节省内存)
Jun 17 Python
python 模块导入问题汇总
Feb 01 Python
详解python的数字类型变量与其方法
Nov 20 #Python
python中异常报错处理方法汇总
Nov 20 #Python
详解MySQL数据类型int(M)中M的含义
Nov 20 #Python
python制作websocket服务器实例分享
Nov 20 #Python
Flask框架的学习指南之用户登录管理
Nov 20 #Python
Flask框架的学习指南之制作简单blog系统
Nov 20 #Python
Flask框架的学习指南之开发环境搭建
Nov 20 #Python
You might like
一个简单的自动发送邮件系统(一)
2006/10/09 PHP
PHP数组排序函数合集 以及它们之间的联系分析
2013/06/27 PHP
PHP实现生成唯一编号(36进制的不重复编号)
2014/07/01 PHP
Zend Framework分页类用法详解
2016/03/22 PHP
Yii2框架视图(View)操作及Layout的使用方法分析
2019/05/27 PHP
php的单例模式及应用场景详解
2021/02/27 PHP
由document.body和document.documentElement想到的
2009/04/13 Javascript
jQuery Ajax文件上传(php)
2009/06/16 Javascript
javascript trim函数在IE下不能用的解决方法
2014/09/12 Javascript
js点击button按钮跳转到另一个新页面
2014/10/10 Javascript
JS实现从表格中动态删除指定行的方法
2015/03/31 Javascript
微信小程序 Video API实例详解
2016/10/02 Javascript
关于使用axios的一些心得技巧分享
2017/07/02 Javascript
JavaScript使用递归和循环实现阶乘的实例代码
2018/08/28 Javascript
angular ng-model 无法获取值的处理方法
2018/10/02 Javascript
微信小程序实现bindtap等事件传参
2019/04/08 Javascript
scrapyd schedule.json setting 传入多个值问题
2019/08/07 Javascript
OpenLayers3实现图层控件功能
2020/09/25 Javascript
[02:54]DOTA2英雄基础教程 暗影牧师戴泽
2013/12/05 DOTA
Python多线程结合队列下载百度音乐的方法
2015/07/27 Python
Python矩阵常见运算操作实例总结
2017/09/29 Python
Python实现正则表达式匹配任意的邮箱方法
2018/12/20 Python
对django layer弹窗组件的使用详解
2019/08/31 Python
Python实现在Windows平台修改文件属性
2020/03/05 Python
Python3-异步进程回调函数(callback())介绍
2020/05/02 Python
python thrift 实现 单端口多服务的过程
2020/06/08 Python
Pycharm打开已有项目配置python环境的方法
2020/07/03 Python
amazeui页面校验功能的实现代码
2020/08/24 HTML / CSS
中国首家奢侈品O2O网购平台:第五大道奢侈品网
2017/12/14 全球购物
全球500多个机场的接送服务:Suntransfers
2019/06/03 全球购物
大学活动邀请函
2014/01/28 职场文书
师范类求职信
2014/06/21 职场文书
天那边观后感
2015/06/09 职场文书
致运动员加油稿
2015/07/21 职场文书
2016大一新生入学教育心得体会
2016/01/23 职场文书
vue使用refs获取嵌套组件中的值过程
2022/03/31 Vue.js