python多线程专题整理
之前一直没有好好整理一下有关多线程编程的文档。今天拿出来好好整理一下方便将来的应用。
一、基础
你要创建一个线程对象,很简单,只要你的类继承threading.Thread,然后在__init__里首先调用threading.Thread的__init__方法即可。
示例:
import threading
class MyThread(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self, name=threadname)
解释:这个方法就可以创建出来一个空的线程对象出来了,而且指定此线程名称为指定的值。
如何让这个线程干活呢?即这个线程我们是开来做什么事情的?
解决:重写类的run()方法即可,把你要在线程执行时做的事情都放到里面
#-*-utf-8-*-
import threading,time
class MyThread(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self, name=threadname)
告诉这个线程是用来做什么事情的!
def run(self):
for i in range(10):
print i,self.getName()
time.sleep(10)
if __name__ == '__main__':
obj = MyThread("this is my thread")
obj.start()
PS:一个线程在start了之后只是处在了可以运行的状态,他什么时候运行还是由系统来进行调度的。即一个线程在调用start方法之后并没有马上运行,而是处于可运行状态。具体什么时候运行还得看OS什么时候有空来调度它
一般来说当线程对象的run方法执行结束或者在执行中抛出异常的话,那么这个线程就会结束了。系统会自动对“dead”状态线程进行清理。
如果一个线程t1在执行的过程中需要等待另一个线程t2执行结束后才能运行的话那就可以在t1在调用t2的join()方法
(PS: 如果要同时跑两个线程的话就要注意 如果想要让一个线程在另一个线程执行完之后再跑的话就要用到这个方法)
def t1(...):
...
t2.join() (要等t2运行结束 之后才会中止掉)
...
这样t1在执行到t2.join()语句后就会等待t2结束后才会继续运行。
(如果遇到了这个语句之后就会等待状态)
如果t2 里面是一个死循环的话那就可以设置一个超时时间了。
def t1(...):
...
t2.join(10) 等10s
主线程完毕了,而子线程还在跑的话那进程就不会退出,直到所有的子线程结束才会退出
。如果想让子线程跟着 父线程退出的时候一块OVER掉的话就可以给这个子线程设置
setDaemon()方法,参数为bool型。True 。
如果设置为False的话就不会自动说你父线程退了我也得跟着你退。
需要注意的是setDaemon()方法必须在线程对象没有调用start()方法之前调用,否则没效果。
示例:
#-*-utf-8-*-
import threading,time
class MyThread(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self, name=threadname)
def run(self):
for i in range(10):
print i,self.getName()
time.sleep(10)
if __name__ == '__main__':
obj = MyThread("this is my thread")
print obj.getName()
print obj.isDaemon()
obj.start()
print "main is over"
默认obj.isDaemon() 为false! 即主线程退出来了子线程还会继续跑!
所以输出结果为
this is my thread
False
0 this is my thread
main is over 主线程已经退出了 可是子线程还在跑
1 this is my thread
2 this is my thread
……
如果设置一下obj.setDaemon(True) 则主线程一退出子线程就马上退出!
如何来获得与线程有关的信息呢?
获得当前正在运行的线程的引用
running = threading.currentThread()
获得当前所有活动对象(即run方法开始但是未终止的任何线程)的一个列表
threadlist = threading.enumerate()
获得这个列表的长度
threadcount = threading.activeCount()
查看一个线程对象的状态调用这个线程对象的isAlive()方法,返回1代表处于“runnable”状态且没有“dead”
threadflag = threading.isAlive()
二、多个线程实现同步
为了解决多个线程同时去修改共享数据的时候,避免数据不一致的情况发生!
解决方法:加锁
锁的创建:锁对象用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()方法以防万一。
附:threading.Thread 类的属性与方法整理
class Thread(group=None, target=None, name=None, args=(), kwargs={})
构造函数能带有关键字参数被调用。这些参数是:
group 应当为 None,为将来实现ThreadGroup类的扩展而保留。(线程组的实现)
target 是被 run()方法调用的回调对象. 默认应为None, 意味着没有对象被调用。
name 为线程名字。默认,形式为'Thread-N'的唯一的名字被创建,其中N 是比较小的十进制数。
args是目标调用参数的tuple,默认为()。
kwargs是目标调用的参数的关键字dictionary,默认为{}。
如果子线程重写了构造函数,它应保证调用基类的构造函数(Thread.__init__()),在线程中进行其他工作之前。
常用方法:
start()
启动线程活动。
在每个线程对象中最多被调用一次。它安排对象的run() 被调用在一单独的控制线程中。
run()
用以表示线程活动的方法。 (即这个线程你想要用它来做什么事情,可以写在这里)
你可能在子类重写这方法。标准的 run()方法调用作为target传递给对象构造函数的回调对象。 如果存在参数,一系列关键字参数从args和kwargs参数相应地起作用。
join([timeout])
等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 当timeout参数未被设置或者不是None,它应当是浮点数指明以秒计的操作超时值。因为join()总是返回None,你必须调用isAlive()来判别超时是否发生。当timeout 参数没有被指定或者是None时,操作将被阻塞直至线程中止。线程能被join()许多次。线程不能调用自身的join(),因为这将会引起死锁。在线程启动之前尝试调用join()会发生错误。
getName()
返回线程名。
setName(name)
设置线程名。 这名字是只用来进行标识目的的字符串。它没有其他作用。多个线程可以取同一名字。最初的名字通过构造函数设置。
isAlive()
返回线程是否活动的。 大致上,线程从 start()调用开始那点至它的run()方法中止返回时,都被认为是活动的。模块函数enumerate()返回活动线程的列表。(从起动到其中止返回)
isDaemon()
返回线程的守护线程标志。
setDaemon(daemonic)
设置守护线程标志为布尔值daemonic。它必须在start()调用之前被调用。 初始值继承至创建线程。当没有活动的非守护线程时,整个Python程序退出。
threading.RLock 类属性与方法
在threading模块中,定义两种类型的琐:threading.Lock和threading.RLock
import threading
lock = threading.Lock() #Lock对象
lock.acquire()
lock.acquire() #产生了死琐。
lock.release()
lock.release()
import threading
rLock = threading.RLock() #RLock对象
rLock.acquire()
rLock.acquire() #在同一线程内,程序不会堵塞。
rLock.release()
rLock.release()
这两种琐的主要区别是:RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。
(实现锁机制操作)
threading.Condition 类属性与方法
可以把Condiftion理解为一把高级的琐,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。threadiong.Condition在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition还提供了如下方法(特别要注意:这些方法只有在占用琐 (acquire)之后才能调用,否则将会报RuntimeError异常。):(创建了占用锁之后才能去做这里面的动作)
Condition.wait([timeout]):
wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。Condition.notify():
唤醒一个挂起的线程(如果存在挂起的线程)。注意:notify()方法不会释放所占用的琐。Condition.notify_all()
Condition.notifyAll()
唤醒所有挂起的线程(如果存在挂起的线程)。注意:这些方法不会释放所占用的琐。
(唤起挂起的线程)