对于Python中线程问题的简单讲解


Posted in Python onApril 03, 2015

我们将会看到一些在Python中使用线程的实例和如何避免线程之间的竞争。你应当将下边的例子运行多次,以便可以注意到线程是不可预测的和线程每次运行出的不同结果。声明:从这里开始忘掉你听到过的关于GIL的东西,因为GIL不会影响到我想要展示的东西。

示例1

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

import time
import urllib2
 
def get_responses():
 urls = [
  'http://www.google.com',
  'http://www.amazon.com',
  'http://www.ebay.com',
  'http://www.alibaba.com',
  'http://www.reddit.com'
 ]
 start = time.time()
 for url in urls:
  print url
  resp = urllib2.urlopen(url)
  print resp.getcode()
 print "Elapsed time: %s" % (time.time()-start)
 
get_responses()

输出是:
 

http://www.google.com 200
http://www.amazon.com 200
http://www.ebay.com 200
http://www.alibaba.com 200
http://www.reddit.com 200
Elapsed time: 3.0814409256

解释:

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

多线程
 

import urllib2
import time
from threading import Thread
 
class GetUrlThread(Thread):
 def __init__(self, url):
  self.url = url
  super(GetUrlThread, self).__init__()
 
 def run(self):
  resp = urllib2.urlopen(self.url)
  print self.url, resp.getcode()
 
def get_responses():
 urls = [
  'http://www.google.com',
  'http://www.amazon.com',
  'http://www.ebay.com',
  'http://www.alibaba.com',
  'http://www.reddit.com'
 ]
 start = time.time()
 threads = []
 for url in urls:
  t = GetUrlThread(url)
  threads.append(t)
  t.start()
 for t in threads:
  t.join()
 print "Elapsed time: %s" % (time.time()-start)
 
get_responses()

输出:
 

http://www.reddit.com 200
http://www.google.com 200
http://www.amazon.com 200
http://www.alibaba.com 200
http://www.ebay.com 200
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
 
class IncrementThread(Thread):
 def run(self):
  #we want to read a global variable
  #and then increment it
  global some_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)
 
def use_increment_thread():
 threads = []
 for i in range(50):
  t = IncrementThread()
  threads.append(t)
  t.start()
 for t in threads:
  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
 
class IncrementThread(Thread):
 def run(self):
  #we want to read a global variable
  #and then increment it
  global some_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()
 
def use_increment_thread():
 threads = []
 for i in range(50):
  t = IncrementThread()
  threads.append(t)
  t.start()
 for t in threads:
  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
 
class CreateListThread(Thread):
 def run(self):
  self.entries = []
  for i in range(10):
   time.sleep(1)
   self.entries.append(i)
  print self.entries
 
def use_create_list_thread():
 for i in range(3):
  t = CreateListThread()
  t.start()
 
use_create_list_thread()

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

我们使用了Lock(),来看下边的例子。
 

from threading import Thread, Lock
import time
 
lock = Lock()
 
class CreateListThread(Thread):
 def run(self):
  self.entries = []
  for i in range(10):
   time.sleep(1)
   self.entries.append(i)
  lock.acquire()
  print self.entries
  lock.release()
 
def use_create_list_thread():
 for i in range(3):
  t = CreateListThread()
  t.start()
use_create_list_thread()

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

Python 相关文章推荐
在Apache服务器上同时运行多个Django程序的方法
Jul 22 Python
Python django实现简单的邮件系统发送邮件功能
Jul 14 Python
Python基于分水岭算法解决走迷宫游戏示例
Sep 26 Python
Python实现字符串反转的常用方法分析【4种方法】
Sep 30 Python
python 3.6.2 安装配置方法图文教程
Sep 18 Python
Python递归函数 二分查找算法实现解析
Aug 12 Python
python提取照片坐标信息的实例代码
Aug 14 Python
python对验证码降噪的实现示例代码
Nov 12 Python
python ctypes库2_指定参数类型和返回类型详解
Nov 19 Python
使用python实现时间序列白噪声检验方式
Jun 03 Python
Keras自定义实现带masking的meanpooling层方式
Jun 16 Python
python实现杨辉三角的几种方法代码实例
Mar 02 Python
python BeautifulSoup设置页面编码的方法
Apr 03 #Python
用Python编写一个简单的FUSE文件系统的教程
Apr 02 #Python
用Python中的__slots__缓存资源以节省内存开销的方法
Apr 02 #Python
用Python的线程来解决生产者消费问题的示例
Apr 02 #Python
用实例分析Python中method的参数传递过程
Apr 02 #Python
使用优化器来提升Python程序的执行效率的教程
Apr 02 #Python
使用Python脚本对Linux服务器进行监控的教程
Apr 02 #Python
You might like
安健A254立体声随身听的分析与打磨
2021/03/02 无线电
php采集时被封ip的解决方法
2010/08/29 PHP
php数组函数序列之rsort() - 对数组的元素值进行降序排序
2011/11/02 PHP
MySQL的FIND_IN_SET函数使用方法分享
2012/03/27 PHP
smarty内部日期函数html_select_date()用法实例分析
2015/07/08 PHP
php array_pop 删除数组最后一个元素实例
2016/11/02 PHP
PHP中类与对象功能、用法实例解读
2020/03/27 PHP
Docker 安装 PHP并与Nginx的部署实例讲解
2021/02/27 PHP
使用jQuery设置disabled属性与移除disabled属性
2014/08/21 Javascript
AngularJS的表单使用详解
2015/06/17 Javascript
js实现汉字排序的方法
2015/07/23 Javascript
javascript 日期相减-在线教程(附代码)
2017/08/17 Javascript
VueJS事件处理器v-on的使用方法
2017/09/27 Javascript
Angular父组件调用子组件的方法
2018/04/02 Javascript
Vue cli构建及项目打包以及出现的问题解决
2018/08/27 Javascript
jQuery选择器之基本过滤选择器用法实例分析
2019/02/19 jQuery
基于vue写一个全局Message组件的实现
2019/08/15 Javascript
[01:10]DOTA2次级职业联赛 - Fly战队宣传片
2014/12/01 DOTA
[00:55]2015国际邀请赛中国区预选赛5月23日——28日约战上海
2015/05/25 DOTA
解析Mac OS下部署Pyhton的Django框架项目的过程
2016/05/03 Python
python列表使用实现名字管理系统
2019/01/30 Python
OpenCV 轮廓检测的实现方法
2019/07/03 Python
Flask框架钩子函数功能与用法分析
2019/08/02 Python
解决pyshp UnicodeDecodeError的问题
2019/12/06 Python
pycharm实现在子类中添加一个父类没有的属性
2020/03/12 Python
使用anaconda安装pytorch的实现步骤
2020/09/03 Python
H5离线存储Manifest原理及使用
2020/04/28 HTML / CSS
ZWILLING双立人英国网上商店:德国刀具锅具厨具品牌
2018/05/15 全球购物
西班牙用户之间买卖视频游戏的平台:Wakkap
2020/03/21 全球购物
为什么使用接口?
2014/08/13 面试题
自我鉴定的范文
2013/10/03 职场文书
导师推荐信范文
2014/05/09 职场文书
四风问题对照检查材料思想汇报
2014/10/07 职场文书
2014乡党委副书记党建工作汇报材料
2014/11/02 职场文书
情感电台广播稿
2015/08/18 职场文书
ORACLE数据库对long类型字段进行模糊匹配的解决思路
2021/04/07 Oracle