Python线程基础

3. threading

threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象。Python Thread提供了Java Thread的行为的子集;没有优先级、线程组,线程也不能被停止、暂停、恢复、中断。Java Thread中的部分被Python实现了的静态方法在threading中以模块方法的形式提供。

threading 模块提供的常用方法: 
threading.currentThread(): 返回当前的线程变量。 
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

threading模块提供的类:  
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local.

3.1. Thread

Thread是线程类,与Java类似,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run():


  1. # encoding: UTF-8  
  2. import threading  
  3.    
  4. # 方法1:将要执行的方法作为参数传给Thread的构造方法  
  5. def func():  
  6.     print 'func() passed to Thread'  
  7.    
  8. t = threading.Thread(target=func)  
  9. t.start()  
  10.    
  11. # 方法2:从Thread继承,并重写run()  
  12. class MyThread(threading.Thread):  
  13.     def run(self):  
  14.         print 'MyThread extended from Thread'  
  15.    
  16. t = MyThread()  
  17. t.start()  

构造方法: 
Thread(group=None, target=None, name=None, args=(), kwargs={}) 
group: 线程组,目前还没有实现,库引用中提示必须是None; 
target: 要执行的方法; 
name: 线程名; 
args/kwargs: 要传入方法的参数。

实例方法: 
isAlive(): 返回线程是否在运行。正在运行指启动后、终止前。 
get/setName(name): 获取/设置线程名。 
is/setDaemon(bool): 获取/设置是否守护线程。初始值从创建该线程的线程继承。当没有非守护线程仍在运行时,程序将终止。 
start(): 启动线程。 
join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。

一个使用join()的例子:


  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. def context(tJoin):  
  6.     print 'in threadContext.'  
  7.     tJoin.start()  
  8.       
  9.     # 将阻塞tContext直到threadJoin终止。  
  10.     tJoin.join()  
  11.       
  12.     # tJoin终止后继续执行。  
  13.     print 'out threadContext.'  
  14.    
  15. def join():  
  16.     print 'in threadJoin.'  
  17.     time.sleep(1)  
  18.     print 'out threadJoin.'  
  19.    
  20. tJoin = threading.Thread(target=join)  
  21. tContext = threading.Thread(target=context, args=(tJoin,))  
  22.    
  23. tContext.start()  

in threadContext. 
in threadJoin. 
out threadJoin. 
out threadContext.


3.2. Lock

Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。

可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。

构造方法: 
Lock()

实例方法: 
acquire([timeout]): 使线程进入同步阻塞状态,尝试获得锁定。 
release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。


  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. data = 0  
  6. lock = threading.Lock()  
  7.    
  8. def func():  
  9.     global data  
  10.     print '%s acquire lock...' % threading.currentThread().getName()  
  11.       
  12.     # 调用acquire([timeout])时,线程将一直阻塞,  
  13.     # 直到获得锁定或者直到timeout秒后(timeout参数可选)。  
  14.     # 返回是否获得锁。  
  15.     if lock.acquire():  
  16.         print '%s get the lock.' % threading.currentThread().getName()  
  17.         data += 1  
  18.         time.sleep(2)  
  19.         print '%s release lock...' % threading.currentThread().getName()  
  20.           
  21.         # 调用release()将释放锁。  
  22.         lock.release()  
  23.    
  24. t1 = threading.Thread(target=func)  
  25. t2 = threading.Thread(target=func)  
  26. t3 = threading.Thread(target=func)  
  27. t1.start()  
  28. t2.start()  
  29. t3.start()  

3.3. RLock

RLock(可重入锁)是一个可以被同一个线程请求多次的同步指令。RLock使用了“拥有的线程”和“递归等级”的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同次数。

可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用 acquire()/release(),计数器将+1/-1,为0时锁处于未锁定状态。

构造方法: 
RLock()

实例方法: 
acquire([timeout])/release(): 跟Lock差不多。


  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. rlock = threading.RLock()  
  6.    
  7. def func():  
  8.     # 第一次请求锁定  
  9.     print '%s acquire lock...' % threading.currentThread().getName()  
  10.     if rlock.acquire():  
  11.         print '%s get the lock.' % threading.currentThread().getName()  
  12.         time.sleep(2)  
  13.           
  14.         # 第二次请求锁定  
  15.         print '%s acquire lock again...' % threading.currentThread().getName()  
  16.         if rlock.acquire():  
  17.             print '%s get the lock.' % threading.currentThread().getName()  
  18.             time.sleep(2)  
  19.           
  20.         # 第一次释放锁  
  21.         print '%s release lock...' % threading.currentThread().getName()  
  22.         rlock.release()  
  23.         time.sleep(2)  
  24.           
  25.         # 第二次释放锁  
  26.         print '%s release lock...' % threading.currentThread().getName()  
  27.         rlock.release()  
  28.    
  29. t1 = threading.Thread(target=func)  
  30. t2 = threading.Thread(target=func)  
  31. t3 = threading.Thread(target=func)  
  32. t1.start()  
  33. t2.start()  
  34. t3.start()  

3.4. Condition

Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。

可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

构造方法: 
Condition([lock/rlock])

实例方法: 
acquire([timeout])/release(): 调用关联的锁的相应方法。 
wait([timeout]): 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。 
notify(): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 
notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

例子是很常见的生产者/消费者模式:


  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. # 商品  
  6. product = None  
  7. # 条件变量  
  8. con = threading.Condition()  
  9.    
  10. # 生产者方法  
  11. def produce():  
  12.     global product  
  13.       
  14.     if con.acquire():  
  15.         while True:  
  16.             if product is None:  
  17.                 print 'produce...'  
  18.                 product = 'anything'  
  19.                   
  20.                 # 通知消费者,商品已经生产  
  21.                 con.notify()  
  22.               
  23.             # 等待通知  
  24.             con.wait()  
  25.             time.sleep(2)  
  26.    
  27. # 消费者方法  
  28. def consume():  
  29.     global product  
  30.       
  31.     if con.acquire():  
  32.         while True:  
  33.             if product is not None:  
  34.                 print 'consume...'  
  35.                 product = None  
  36.                   
  37.                 # 通知生产者,商品已经没了  
  38.                 con.notify()  
  39.               
  40.             # 等待通知  
  41.             con.wait()  
  42.             time.sleep(2)  
  43.    
  44. t1 = threading.Thread(target=produce)  
  45. t2 = threading.Thread(target=consume)  
  46. t2.start()  
  47. t1.start()  

上面的例子对condition的解释说明有不详细的地方,以下是我的理解

1. 条件变量通常是和互斥量配合使用,threading.Condition([lock]),可以在构造条件变量的时候不指定互斥量,会自动创建一个互斥锁;当几个条件变量共享一个互斥锁时,就要传递这个互斥锁

2. 看一个官网给出的例子

# Consume one item
cv.acquire()
while not an_item_is_available():
    cv.wait()
get_an_available_item()
cv.release()

# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()

在实际编写程序时,往往在这两段代码前包上while True

这段代码问题在于,生产者会不停的生产,有写产品,消费者来不及消费,就被新产品覆盖

wait的内部实现是:

 1. 释放锁

 2. 堵塞线程,直到收到notify或者notifiall的信号

 3. 加锁

所以,在wait之前此线程一定要得到锁了,否则会有异常


3. notify或者notifyall在调用之前也一定要加锁,否则有异常

notify会从所有等待线程中选择一个唤醒

notifyall会唤醒所有等待线程,但是这些线程要竞争得到一个锁,因为wait的第3步是一个加锁的步骤


上面的代码比较trick的应用了wait这一个特性,

生产者在生成一个产品后,wait,就释放了锁,消费者得到锁之后,解除wait, 消费一个产品,再次进入wait, 释放锁,此时生产者也解除了wait


我的代码如下:

  1. count = 0  
  2.   
  3. def producer():  
  4.     global product, count  
  5.     while True:  
  6.         con.acquire()  
  7.         while product is not None:  
  8.             con.wait()  
  9.         count += 1  
  10.         product = count  
  11.         print 'producing %d' %product  
  12.         con.notify()  
  13.         time.sleep(2)  
  14.         con.release()  
  15.   
  16. def consumer():  
  17.     global product, count  
  18.     while True:  
  19.         con.acquire()  
  20.         while product is None:  
  21.             con.wait()  
  22.         print 'consuming %d' %count  
  23.         time.sleep(2)  
  24.         product = None  
  25.         con.notify()  
  26.         con.release()  

用条件变量实现生产者/消费者在逻辑上不够简洁,用semaphore比较清楚,请见

http://blog.csdn.net/sunmenggmail/article/details/7679152


3.5. Semaphore/BoundedSemaphore

Semaphore(信号量)是计算机科学史上最古老的同步指令之一。Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release() 时+1。计数器不能小于0;当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。

基于这个特点,Semaphore经常用来同步一些有“访客上限”的对象,比如连接池。

BoundedSemaphore 与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

构造方法: 
Semaphore(value=1): value是计数器的初始值。

实例方法: 
acquire([timeout]): 请求Semaphore。如果计数器为0,将阻塞线程至同步阻塞状态;否则将计数器-1并立即返回。 
release(): 释放Semaphore,将计数器+1,如果使用BoundedSemaphore,还将进行释放次数检查。release()方法不检查线程是否已获得 Semaphore。


  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. # 计数器初值为2  
  6. semaphore = threading.Semaphore(2)  
  7.    
  8. def func():  
  9.       
  10.     # 请求Semaphore,成功后计数器-1;计数器为0时阻塞  
  11.     print '%s acquire semaphore...' % threading.currentThread().getName()  
  12.     if semaphore.acquire():  
  13.           
  14.         print '%s get semaphore' % threading.currentThread().getName()  
  15.         time.sleep(4)  
  16.           
  17.         # 释放Semaphore,计数器+1  
  18.         print '%s release semaphore' % threading.currentThread().getName()  
  19.         semaphore.release()  
  20.    
  21. t1 = threading.Thread(target=func)  
  22. t2 = threading.Thread(target=func)  
  23. t3 = threading.Thread(target=func)  
  24. t4 = threading.Thread(target=func)  
  25. t1.start()  
  26. t2.start()  
  27. t3.start()  
  28. t4.start()  
  29.    
  30. time.sleep(2)  
  31.    
  32. # 没有获得semaphore的主线程也可以调用release  
  33. # 若使用BoundedSemaphore,t4释放semaphore时将抛出异常  
  34. print 'MainThread release semaphore without acquire'  
  35. semaphore.release()  


3.6. Event

Event(事件)是最简单的线程通信机制之一:一个线程通知事件,其他线程等待事件。Event内置了一个初始为False的标志,当调用set()时设为True,调用clear()时重置为 False。wait()将阻塞线程至等待阻塞状态。

Event其实就是一个简化版的 Condition。Event没有锁,无法使线程进入同步阻塞状态。

构造方法: 
Event()

实例方法: 
isSet(): 当内置标志为True时返回True。 
set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。 
clear(): 将标志设为False。 
wait([timeout]): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。



  1. # encoding: UTF-8  
  2. import threading  
  3. import time  
  4.    
  5. event = threading.Event()  
  6.    
  7. def func():  
  8.     # 等待事件,进入等待阻塞状态  
  9.     print '%s wait for event...' % threading.currentThread().getName()  
  10.     event.wait()  
  11.       
  12.     # 收到事件后进入运行状态  
  13.     print '%s recv event.' % threading.currentThread().getName()  
  14.    
  15. t1 = threading.Thread(target=func)  
  16. t2 = threading.Thread(target=func)  
  17. t1.start()  
  18. t2.start()  
  19.    
  20. time.sleep(2)  
  21.    
  22. # 发送事件通知  
  23. print 'MainThread set event.'  
  24. event.set()  

注意set,会将event设为True,然后所有wait的线程都会被唤醒

上面的代码输出是:

Thread-2 waiting for event...
Thread-1 waiting for event...
mainthread set event
Thread-1 recv event
Thread-2 recv event

而不是只有一个线程被唤醒


3.7. Timer

Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。

构造方法: 
Timer(interval, function, args=[], kwargs={}) 
interval: 指定的时间 
function: 要执行的方法 
args/kwargs: 方法的参数

实例方法: 
Timer从Thread派生,没有增加实例方法。

  1. # encoding: UTF-8  
  2. import threading  
  3.    
  4. def func():  
  5.     print 'hello timer!'  
  6.    
  7. timer = threading.Timer(5, func)  
  8. timer.start()  

3.8. local

local是一个小写字母开头的类,用于管理 thread-local(线程局部的)数据。对于同一个local,线程无法访问其他线程设置的属性;线程设置的属性不会被其他线程设置的同名属性替换。

可以把local看成是一个“线程-属性字典”的字典,local封装了从自身使用线程作为 key检索对应的属性字典、再使用属性名作为key检索属性值的细节。


  1. local = threading.local()  
  2.   
  3.   
  4. local.ttname = 'main'  
  5. def func():  
  6. <span style="white-space:pre">  </span>local.ttname = '%s ' %threading.currentThread().getName()  
  7. <span style="white-space:pre">  </span>print local.ttname  
  8. <span style="white-space:pre">  </span>  
  9.   
  10.   
  11.   
  12.   
  13. t1 = threading.Thread(target = func)  
  14. t2 = threading.Thread(target = func)  
  15.   
  16.   
  17. t1.start()  
  18. t2.start()<span style="white-space:pre">    </span>  
  19.   
  20.   
  21. print local.ttname  


输出:

Thread-1 
Thread-2 
 main


注意local的特殊性,对于同一个local,线程改变的属性只是自己线程内local的属性,而其他线程不收到这个线程的影响

如果local只是一个普通类的一个实例,那么所有线程共享local的属性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值