3个实例帮你理解Python中的线程

标签: 实例 理解 python | 发表时间:2013-11-26 16:22 | 作者:
出处:http://www.iteye.com
我们将会看到一些在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()

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

原文: Understanding Threads in Python / 翻译: acmerfight

感谢 tuhaihe 投递这篇资讯

已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [实例 理解 python] 推荐:

3个实例帮你理解Python中的线程

- - ITeye资讯频道
我们将会看到一些在Python中使用线程的实例和如何避免线程之间的竞争. 你应当将下边的例子运行多次,以便可以注意到线程是不可预测的和线程每次运行出的不同结果. 声明:从这里开始忘掉你听到过的关于GIL的东西,因为GIL不会影响到我想要展示的东西. 我们将要请求五个不同的url:. 除非cpu从一个url获得了回应,否则不会去请求下一个url.

从底层理解Python的执行

- - 外刊IT评论
最近我在学习 Python 的运行模型. 我对 Python 的一些内部机制很是好奇,比如 Python 是怎么实现类似 YIELD VALUE、YIELDFROM. 这样的操作码的;对于 递推式构造列表(List Comprehensions)、生成器表达式(generator expressions)以及其他一些有趣的.

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

- - Seay's blog 小屌丝守护着信息安全、
我们将会看到一些在Python中使用线程的实例和如何避免线程之间的竞争. 你应当将下边的例子运行多次,以便可以注意到线程是不可预测的和线程每次运行出的不同结果. 声明:从这里开始忘掉你听到过的关于GIL的东西,因为GIL不会影响到我想要展示的东西. 我们将要请求五个不同的url:. 除非cpu从一个url获得了回应,否则不会去请求下一个url.

dropbox讲python

- chuang - Initiative
dropbox定制优化CPython虚拟机,自己搞了个malloc调度算法. 那个 !!!111cos(0). 期待这次PyCon China 2011.

Python调试

- - 企业架构 - ITeye博客
原文地址: http://blog.csdn.net/xuyuefei1988/article/details/19399137. 1、下面网上收罗的资料初学者应该够用了,但对比IBM的Python 代码调试技巧:. IBM:包括 pdb 模块、利用 PyDev 和 Eclipse 集成进行调试、PyCharm 以及 Debug 日志进行调试:.

Python WSGI 初探

- - 坚实的幻想
在构建 Web 应用时,通常会有 Web Server 和 Application Server 两种角色. 其中 Web Server 主要负责接受来自用户的请求,解析 HTTP 协议,并将请求转发给 Application Server,Application Server 主要负责处理用户的请求,并将处理的结果返回给 Web Server,最终 Web Server 将结果返回给用户.

Python实现逻辑回归(Logistic Regression in Python)

- - 神刀安全网
Logistic Regression in Python ,作了中文翻译,并相应补充了一些内容. 本文并不研究逻辑回归具体算法实现,而是使用了一些算法库,旨在帮助需要用Python来做逻辑回归的训练和预测的读者快速上手. 逻辑回归是一项可用于预测二分类结果(binary outcome)的统计技术,广泛应用于金融、医学、犯罪学和其他社会科学中.

python 下载文件

- Eric - python相关的python 教程和python 下载你可以在老王python里寻觅
之前给大家分享的python 多线程抓取网页,我觉的大家看了以后,应该会对python 抓取网页有个很好的认识,不过这个只能用python 来抓取到网页的源代码,如果你想用做python 下载文件的话,上面的可能就不适合你了,最近我在用python 做文件下载的时候就遇到这个问题了,不过最终得以解决,为了让大家以后碰过这个问题有更好的解决办法,我把代码发出来:.

python代码调试

- - 阿里古古
【转自: http://blog.csdn.net/luckeryin/article/details/4477233】. 本文讨论在没有方便的IDE工具可用的情况下,使用pdb调试python程序. 例如,有模拟税收计算的程序:. debug_demo函数计算4500的入账所需的税收. 在需要插入断点的地方,加入红色部分代码:如果_DEBUG值为True,则在该处开始调试(加入_DEBUG的原因是为了方便打开/关闭调试).

python编程规范

- - 互联网 - ITeye博客
@FileName: @Author:[email protected] @Create date: @description:用一行文字概述模块或脚本,用句号结尾. 不影响编码的效率,不与大众习惯冲突.. 使代码的逻辑更清晰,更易于理解..   *所有的 Python 脚本文件都应在文件头标上如下标识或其兼容格式的标识.