Python多线程编程

本文转自http://crazier9527.javaeye.com/blog/442100

 

我们在做软件开发的时候很多要用到多线程技术。例如如果做一个下载软件象flashget就要用到、象在线视频工具realplayer也 要用到因为要同时下载media stream还要播放。其实例子是很多的。

线程相对进程来说是“轻量级”的,操作系统用较少的资源创建和管理线程。程序中的线程在相同的内存空间中执行,并共享许多相同的资源。

python 中如何创建一个线程对象

如果你要创建一个线程对象,很简单,只要你的类继承threading.Thread , 然后在__init__ 里首先调用threading.Thread__init__ 方法即可

import threading
class mythread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name = threadname)
….

这才仅仅是个空线程,我可不是要他拉空车的,他可得给我干点实在活。很简单,重写类的run() 方法即可,把你要在线程执行时做的事情都放到里面

import threading
import time
class mythread(threading.Thread):
def __init__(…):
….
def run(self):
for i in range(10):
print self.getName, i
time.sleep(1)

以上代码我们让这个线程在执行之后每隔1秒输出一次信息到屏幕,10次后结束

getName()threading.Thread 类的一个方法,用来获得这个线程对 象的name 。还有一个方法setName() 当然就是来设置这个线程对象的name 的了。

如果要创建一个线程,首先就要先创建一个线程对象

mythread1 = mythread(’mythread 1′)

一个线程对象被创建后,他就处于“born ”(诞 生状态)

如何让这个线程对象开始运行呢?只要调用线程对象的start() 方 法即可

mythread1.start()

现在线程就处于“ready ”状态或者也称为“runnable ”状态。

奇怪吗?不是已经start 了吗?为什么不称为“running ”状态呢?其实是有原因的。因为我们的计算机一般是 不具有真正并行处理能力的。我们所谓的多线程只是把时间分成片段,然后隔一个时间段就让一个线程执行一下,然后进入“sleeping ”状态,然后唤醒另一个在“sleeping ”的线程,如此循环runnable->sleeping->runnable… ,只是因为计算机执行速度很快,而时间片段间隔很小,我们感受不到,以为是同时进行的。所以说一个线程在start 了之后只是处在了可以运行的状态,他什么时候运行还是由系统来进行调度的。

那一个线程什么时候会“dead ”呢?一般来说当 线程对象的run 方法执行结束或者在执行中抛出异常的 话,那么这个线程就会结束了。系统会自动对“dead ” 状态线程进行清理。

如果一个线程t1 在执行的过程中需要等待另一个线 程t2 执行结束后才能运行的话那就可以在t1 在调用t2join() 方 法

….
def t1(…):

t2.join()

这样t1 在执行到t2.join() 语句后就会等待t2 结束后才会继续运行。

但是假如t1 是个死循环的话那么等待就没有意义 了,那怎么办呢?可以在调用t2join() 方法的时候给一个浮点数做超时参数,这样这个线程就不会 等到花儿也谢了了。我等你10s,你不回来我还不允许我改嫁啊? :)

def t1(…):

t2.join(10)

 

############自己加的备注###############

在结合线程和队列使用的情况中,除了使用threading.Thread的join()以外,还可以选择使用Queue.Queue的join()方法。前者代表等待该线程结束,后者代表等待该队列中所有的元素被处理完毕。

需要注意的是,如果使用队列中的join()方法时,一定要记得在每次线程处理结束的时候调用task_done()方法。

下面贴一段PYTHON手册上的内容:

 

Queue. task_done ( )

Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.

If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).

Raises a ValueError if called more times than there were items placed in the queue.

New in version 2.5.

Queue. join ( )

Blocks until all items in the queue have been gotten and processed.

The count of unfinished tasks goes up whenever an item is added to the queue. The count goes down whenever a consumer thread calls task_done() to indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks.

New in version 2.5.

###########################


如果一个进程的主线程运行完毕而子线程还在执行的话,那么进程就不会退出,直到所有子线程结束为止,如何让主线程结束的时候其他子线程也乖乖的跟老 大撤退呢?那就要把那些不听话的人设置为听话的小弟,使用线程对象的setDaemon() 方 法,参数为bool 型。True 的话就代表你要听话,我老大(主线程)扯呼,你也要跟着撤, 不能拖后腿。如果是False 的话就不用那么听话了,老 大允许你们将在外军命有所不受的。需要注意的是setDaemon() 方 法必须在线程对象没有调用start() 方法之前调用, 否则没效果。

t1 = mythread(’t1′)
print t1.getName(),t1.isDaemon()
t1.setDaemon(True)
print t1.getName(),t1.isDaemon()
t1.start()
print ‘main thread exit’

当执行到 print ‘main thread exit’ 后, 主线程就退出了,当然t1 这个线程也跟着结束了。但是如 果不使用t1 线程对象的setDaemon() 方法的话,即便主线程结束了,还要等待t1线 程自己结束才能退出进程。isDaemon() 是用来获 得一个线程对象的Daemonflag 状态的。

如何来获得与线程有关的信息呢?

获得当前正在运行的线程的引用

running = threading.currentThread()

获得当前所有活动对象(即run 方法开始但是未终 止的任何线程)的一个列表

threadlist = threading.enumerate()

获得这个列表的长度

threadcount = threading.activeCount()

查看一个线程对象的状态调用这个线程对象的isAlive() 方 法,返回1代表处于“runnable ”状态且没有“dead

threadflag = threading.isAlive()

Python线程编程(二)简单的线程同步

多个执行线程经常要共享数据,如果仅仅读取共享数据还好,但是如果多个线程要修改共享数据的话就可能出现无法预料的结果。

假如两个线程对象t1t2 都要对数值num=0 进行增1运算,那么t1t2 都各对num 修改10 次的话,那么num 最 终的结果应该为20 。但是如果当t1 取得num 的值时(假如此时num0 ),系统把t1 调度为“sleeping ” 状态,而此时t2 转换为“running ”状态,此时t2 获得的num 的 值也为0 ,然后他把num+1 的值1 赋给num 。 系统又把t2 转化为“sleeping ”状态,t1 为“running ” 状态,由于t1 已经得到num 值为0 ,所以他也把num+1 的 值赋给了num1 。本来是2 次增1 运 行,结果却是num 只增了1 次。类似这样的情况在多线程同时执行的时候是有可能发生的。所以为 了防止这类情况的出现就要使用线程同步机制。

最简单的同步机制就是“锁”

锁对象用threading.RLock 类创建

mylock = threading.RLock()

如何使用锁来同步线程呢?线程可以使用锁的acquire() (获得)方法,这样锁就进入“locked ”状态。每 次只有一个线程可以获得锁。如果当另一个线程试图获得这个锁的时候,就会被系统变为“blocked ”状态,直到那个拥有锁的线程调用锁的release() (释放)方法,这样锁就会进入“unlocked ”状态。“blocked ” 状态的线程就会收到一个通知,并有权利获得锁。如果多个线程处于“blocked ” 状态,所有线程都会先解除“blocked ”状态,然后 系统选择一个线程来获得锁,其他的线程继续沉默(“blocked ”)。

import threading
mylock = threading.RLock()
class mythread(threading.Thread)

def run(self …):
…     #此处 不可以 放置修改共享数据的代码
mylock.acquire()
…     #此处 可以 放置修改共享数据的代码
mylock.release()
#此处 不可以 放置修改共享数据的代码

我们把修改共享数据的代码称为“临界区”,必须将所有“临界区”都封闭在同一锁对象的acquire()release() 方 法调用之间。

锁只能提供最基本的同步级别。有时需要更复杂的线程同步,例如只在发生某些事件时才访问一个临界区(例如当某个数值改变时)。这就要使用“条件变 量”。

条件变量用threading.Condition 类 创建

mycondition = threading.Condition()

条件变量是如何工作的呢?首先一个线程成功获得一个条件变量后,调用此条件变量的wait() 方法会导致这个线程释放这个锁,并进入“blocked ”状态,直到另一个线程调用同一个条件变量的notify() 方法来唤醒那个进入“blocked ”状态的线程。如果调用这个条件变量的notifyAll() 方法的话就会唤醒所有的在等待的线程。

如果程序或者线程永远处于“blocked ”状态 的话,就会发生死锁。所以如果使用了锁、条件变量等同步机制的话,一定要注意仔细检查,防止死锁情况的发生。对于可能产生异常的临界区要使用异常处理机制 中的finally 子句来保证释放锁。等待一个条件变量 的线程必须用notify() 方法显式的唤醒,否则就永 远沉默。保证每一个wait() 方法调用都有一个相对应 的notify() 调用,当然也可以调用notifyAll() 方法以防万一。

Python线程编程(三)同步队列

我 们经常会采用生产者/消费者关系的两个线程来处理一个共享缓冲区的数据。例如一个生产者线程接受用户数据放入一个共享缓冲区里,等待一个消费者线程对数据 取出处理。但是如果缓冲区的太小而生产者和消费者两个异步线程的速度不同时,容易出现一个线程等待另一个情况。为了尽可能的缩短共享资源并以相同速度工作 的各线程的等待时间,我们可以使用一个“队列”来提供额外的缓冲区。

创建一个“队列”对象

import Queue
myqueue = Queue.Queue(maxsize = 10)
Queue.Queue 类 即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue 的 构造函数的可选参数maxsize 来设定队列长度。如果maxsize 小于1 就表示队列长度无限。

 

将一个值放入队列中

myqueue.put(10)

调 用队列对象的put() 方法在队尾插入一个项目。put() 有两个参数,第一个item 为必需的,为插入项目的值;第二个block 为可选参数,默认为1 。如果队列当前为空且block1put() 方法就使调用线程暂停,直到空出一个数据单元。如果block0put 方 法将引发Full 异常。

将一个值从队列中取出

myqueue.get()

调用 队列对象的get() 方法从队头删除并返回一个项目。可 选参数为block ,默认为1 。如果队列为空且block1get() 就使调用线程暂停,直至有项目可用。如果block 为0,队列将引发Empty 异常。

我们用一个例子来展示如何使用Queue # queue_example.py
from Queue import Queue
import threading
import random
import time

 

# Producer thread
class Producer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),’adding’,i,’to queue’
self.sharedata.put(i)
time.sleep(random.randrange(10)/10.0)
print self.getName(),’Finished’

# Consumer thread
class Consumer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),’got a value:’,self.sharedata.get()
time.sleep(random.randrange(10)/10.0)
print self.getName(),’Finished’

# Main thread
def main():
queue = Queue()
producer = Producer(’Producer’, queue)
consumer = Consumer(’Consumer’, queue)

print ‘Starting threads …’
producer.start()
consumer.start()

producer.join()
consumer.join()

print ‘All threads have terminated.’

if __name__ == ‘__main__’:
main()

示例代码中实现了两个类:生产者类Producer 和 消费者类Consumer 。前者在一个随机的时间内放入 一个值到队列queue 中然后显示出来,后者在一定随机 的时间内从队列queue 中取出一个值并显示出来。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值