Python-多线程学习总结
我们在做软件开发的时候很多要⽤到多线程技术。例如如果做⼀个下载软件象flashget就要⽤到、象在线视频⼯具realplayer也要⽤到因为要同时下载media stream还要播放。其实例⼦是很多的。线程相对进程来说是“轻量级”的,操作系统⽤较少的资源创建和管理线程。程序中的线程在相同的内存空间中执⾏,并共享许多相同的资源。
使⽤threading.Thread类创建线程(此处参考⾃:)
__init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None)
  *group* 应该为None, 为未来扩展ThreadGroup预留
  *target* 是随时被run()⽅法调⽤的可调⽤对象, 默认为None, 表明没有对象被调⽤
  *name* 为线程名字, 默认名字为Thread-N, 其中N为⽐较⼩的⼗进制数
  *args* list参数
  *kwargs* dict参数
getName(self) 返回线程的名字 setName(self, name) 设置线程的名字
isAlive(self) 表⽰这个线程是否还在运⾏中
isDaemon(self) 是否是守护线程 setDeamon(self, bool) 设置守护线程与否
join(self, timeout=None) 程序挂起, 直到线程结束, 如果给了timeout, 则最多阻塞 timeout 秒
  1).等待线程终⽌, 阻塞调⽤此线程的线程, 直到其终⽌——正常或通过⼀个未处理的异常, 或直到可选超时
  2).当超时参数被指定时, 它应该是⼀个浮点数, 作为 join() 总是返回 None, 必须在调⽤ join() 之后调⽤ isAlive() ⽅法来判断是否超时——如果线程还活着, 这时候 join() 调⽤超时
run(self) 定义线程功能的函数, ⼀般被重写, ⽆参数
start(self) 线程开始执⾏, 会⾃动调⽤ run() ⽅法
在python中如何创建⼀个线程对象
如果你要创建⼀个线程对象,很简单,只要你的类继承threading.Thread,然后在__init__⾥⾸先调⽤thr
eading.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):
Name(), i
time.sleep(1)
以上代码我们让这个线程在执⾏之后每隔1秒输出⼀次信息到屏幕,10次后结束,⽽继承threading.Thread的类也只重写__init__(), run()这两个⽅法即可,当然也可以加其他⾃定义⽅法。
getName()是threading.Thread类的⼀个⽅法,⽤来获得这个线程对象的name。还有⼀个⽅法setName()当然就是来设置这个线程对象的name的了。
如果要创建⼀个线程,⾸先就要先创建⼀个线程对象
mythread1 = mythread('mythread 1')
⼀个线程对象被创建后,他就处于“born”(诞⽣状态)
如何让这个线程对象开始运⾏呢?只要调⽤线程对象的start()⽅法即可,start()⽅法会⾃动调⽤线程对象的run()⽅法
mythread1.start()
现在线程就处于“ready”状态或者也称为“runnable”状态。
奇怪吗?不是已经start了吗?为什么不称为“running”状态呢?其实是有原因的。因为我们的计算机⼀般是不具有真正并⾏处理能⼒的。我们所谓的多线程只是把时间分成⽚段,然后隔⼀个时间段就让⼀个线程执⾏⼀下,然后进⼊“sleeping ”状态,然后唤醒另⼀个在“sleeping”的线程,如此循环runnable->sleeping-& ,只是因为计算机执⾏速度很快,⽽时间⽚段间隔很⼩,我们感受不到,以为是同时进⾏的。所以说⼀个线程在start了之后只是处在了可以运⾏的状态,他什么时候运⾏还是由系统来进⾏调度的。
那⼀个线程什么时候会“dead”呢?⼀般来说当线程对象的run⽅法执⾏结束或者在执⾏中抛出异常的话,那么这个线程就会结束了。系统会⾃动对“dead”状态线程进⾏清理。
如果⼀个线程t1在执⾏的过程中需要等待另⼀个线程t2执⾏结束后才能运⾏的话那就可以在t1在调⽤t2的join()⽅法
.
...
def t1(...):
...
t2.join()
...
这样t1在执⾏到t2.join()语句后就会等待t2结束后才会继续运⾏。
但是假如t2是个死循环的话那么等待就没有意义了,那怎么办呢?可以在调⽤t2的join()⽅法的时候给⼀个浮点数做超时参数,这样这个线程就不会等到花⼉也谢了了。我等你10s,你不回来我还不允许我改嫁啊?:)
def t1(...):
...
t2.join(10)
...
如果⼀个进程的主线程运⾏完毕⽽⼦线程还在执⾏的话,那么进程就不会退出,直到所有⼦线程结束为⽌,如何让主线程结束的时候其他⼦线程也乖乖的跟⽼⼤撤退呢?那就要把那些不听话的⼈设置为听话的⼩弟,使⽤线程对象的setDaemon()⽅法,参数为bool型。True的话就代表你要听话,我⽼⼤(主线程)扯呼,你也要跟着撤,不能拖后腿。如果是False的话就不⽤那么听话了,⽼⼤允许你们将在外军命有所不受的。需要注意的是setDaemon()⽅法必须在线程对象没有调⽤start()⽅法之前调⽤,否则没效果。
t1 = mythread('t1')
Name(),t1.isDaemon()
t1.setDaemon(True)
Name(),t1.isDaemon()
t1.start()
print'main thread exit'
当执⾏到 print 'main thread exit' 后,主线程就退出了,当然t1这个线程也跟着结束了。但是如果不使⽤t1线程对象的setDaemon()⽅法的话,即便主线程结束了,还要等待t1线程⾃⼰结束才能退出进程。isDaemon()是⽤来获得⼀个线程对象的Daemonflag状态的。
如何来获得与线程有关的信息呢?
获得当前正在运⾏的线程的引⽤
running = threading.currentThread()
获得当前所有活动对象(即run⽅法开始但是未终⽌的任何线程)的⼀个列表
threadlist = umerate()
获得这个列表的长度
threadcount = threading.activeCount()
查看⼀个线程对象的状态调⽤这个线程对象的isAlive()⽅法,返回1代表处于“runnable”状态且没有“dead”
threadflag = threadingObj.isAlive()
多个执⾏线程经常要共享数据,如果仅仅读取共享数据还好,但是如果多个线程要修改共享数据的话就可能出现⽆法预料的结果。
假如两个线程对象t1和t2都要对数值num=0进⾏增1运算,那么t1和t2都各对num修改10次的话,那么num最终的结果应该为20。但是如果当t1取得num的值时(假如此时num为0),系统把t1调度为“sleeping”状态,⽽此时t2转换为“running”状态,此时t2获得的num的值也为0,然后他把num+1的值1赋给num。系统⼜把t2转化为“sleeping”状态,t1为“running”状态,由于t1已经得到num值为0,所以他也把num+1的值赋给了num为1。本来是2次增1运⾏,结果却是num只增了1次(⽤锁的原因, 系统没准⼉在哪个线程执⾏的时候断开去跟别的线程好了)。类似这样的情况在多线程同时执⾏的时候是有可能发⽣的。所以为了防⽌这类情况的出现就要使⽤线程同步机制。
最简单的同步机制就是“锁”
锁对象⽤threading.RLock或threading.Lock类创建(其中Lock不允许在acquire之后再次acquire, ⽽RLock可以, 但是必须成对release)
mylock = threading.RLock()
如何使⽤锁来同步线程呢?线程可以使⽤锁的acquire() (获得)⽅法,这样锁就进⼊“locked”状态。每次只有⼀个线程可以获得锁(所以可以把修改共享数据的代码放在获得锁acquire和释放锁release之间, 因为只有⼀个线程会获得锁并执⾏其中代码, 即只有⼀个线程在获得值并修改值)。如果当另⼀个线程试图获得这个锁的时候,就会被系统变为“blocked”状态,直到那个拥有锁的线程调⽤锁的release() (释放)⽅法,这样锁就会进⼊“unlocked”状态。“blocked”状态的线程就会收到⼀个通知,并有权利获得锁。如果多个线程处于“blocked”状态,所有线程都会先解除“blocked”状态,然后系统选择⼀个线程来获得锁,其他的线程继续沉默(“blocked”)。
import threading
mylock = threading.RLock()
class mythread(threading.Thread)
...
def run(self ...):
...    #此处不可以放置修改共享数据的代码
mylock.acquire()
...    #此处可以放置修改共享数据的代码
...    #此处不可以放置修改共享数据的代码
我们把修改共享数据的代码称为“临界区”,必须将所有“临界区”都封闭在同⼀锁对象的acquire()和release()⽅法调⽤之间。
锁只能提供最基本的同步级别。有时需要更复杂的线程同步,例如只在发⽣某些事件时才访问⼀个临界区(例如当某个数值改变时)。这就要使⽤“条件变量”。
条件变量⽤threading.Condition类创建
mycondition = threading.Condition()
条件变量是如何⼯作的呢?⾸先⼀个线程成功获得⼀个条件变量后,调⽤此条件变量的wait()⽅法会导致这个线程释放这个锁,并进
⼊“blocked”状态,直到另⼀个线程调⽤同⼀个条件变量的notify()⽅法来唤醒那个进⼊“blocked”状态的线程。如果调⽤这个条件变量的notifyAll()⽅法的话就会唤醒所有的在等待的线程。
如果程序或者线程永远处于“blocked”状态的话,就会发⽣死锁。所以如果使⽤了锁、条件变量等同步机制的话,⼀定要注意仔细检查,防⽌死锁情况的发⽣。对于可能产⽣异常的临界区要使⽤异常处理机制中的finally⼦句来保证释放锁(避免出错跳出, ⽽未释放锁!!)。等待⼀个条件变量的线程必须⽤notify()⽅法显式的唤醒,否则就永远沉默。保证每⼀个wait()⽅法调⽤都有⼀个相对应的notify()调⽤,当然也可以调⽤notifyAll()⽅法以防万⼀。
我们也经常会采⽤⽣产者/消费者关系的两个线程来处理⼀个共享缓冲区的数据。例如⼀个⽣产者线程接受⽤户数据放⼊⼀个共享缓冲区⾥,等待⼀个消费者线程对数据取出处理。但是如果缓冲区的太⼩⽽⽣产者和消费者两个异步线程的速度不同时,容易出现⼀个线程等待另⼀个情况。为了尽可能的缩短共享资源并以相同速度⼯作的各线程的等待时间,我们可以使⽤⼀个“队列”来提供额外的缓冲区。
创建⼀个“队列”对象
import Queue
myqueue = Queue.Queue(maxsize = 10)
Queue.Queue类即是⼀个队列的同步实现。队列长度可为⽆限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize⼩于1就表⽰队列长度⽆限。
将⼀个值放⼊队列中
myqueue.put(10)
调⽤队列对象的put()⽅法在队尾插⼊⼀个项⽬。put()有两个参数,第⼀个item为必需的,为插⼊项⽬的值;第⼆个block为可选参数,默认为1。如果队列当前为空且block为1,put()⽅法就使调⽤线程暂停,直到空出⼀个数据单元(此处空是由于达到maxsize上限)。如果block为
0,put⽅法将引发Full异常。
将⼀个值从队列中取出
<()
调⽤队列对象的get()⽅法从队头删除并返回⼀个项⽬。可选参数为block,默认为1。如果队列为空且block为1,get()就使调⽤线程暂停,⼀直阻塞,直到从queue中获取到⼀个值。如果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):
Name(),'adding',i,'to queue'
self.sharedata.put(i)
time.sleep(random.randrange(10)/10.0)
Name(),'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):
Name(),'got a value:',()
time.sleep(random.randrange(10)/10.0)
Name(),'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()
PS:main()中可以把以下两⾏交换(由于get()时默认没有get到就阻塞, 直到get成功), ⽽如果Producer中for i in range(20)的20改为19则consumer就会⼀直处于阻塞状态
producer = Producer('Producer', queue)
consumer = Consumer('Consumer', queue)
⼀般使⽤Queue的步骤如下:
1.创建⼀个 Queue.Queue() 的实例,然后使⽤数据对它进⾏填充。
2.将经过填充数据的实例传递给线程类,后者是通过继承 threading.Thread 的⽅式创建的。
3.⽣成守护线程池。
4.每次从队列中取出⼀个项⽬,并使⽤该线程中的数据和 run ⽅法以执⾏相应的⼯作。
5.在完成每个项⽬之后,使⽤ queue.task_done() 函数向任务已经完成的队列发送⼀个信号。
6.在主程序中对队列执⾏ join 操作(保持阻塞状态,直到处理了队列中的所有项⽬为⽌。在将⼀个项⽬添加到该队列时,未完成的任务的总数就会增加。当使⽤者线程调⽤ task_done() 以表⽰检索了该项⽬、并完成了所有的⼯作时,那么未完成的任务的总数就会减少。当未完成的任务的总数减少到零时,join() 就会结束阻塞状态),实际上意味着等到队列全部task_done()或者pty()(即队列为空),再退出主程序。
7.也可不⽤queue在主程序中join, ⽽⽤每个线程在主程序中join, 等每个线程run(while True, 如果队列为空(queue.Empty())则break, 否则get(),继续执⾏)完后退出主程序(既可以⽤queue.task_done()与queue.join()配合, ⼜可以⽤每个线程.join(), 不过有了queue谁还⽤每个线程去join呢~~)
关于join()
1 join⽅法的作⽤是阻塞主进程⽆法执⾏join以后的语句,专注执⾏多线程,必须等待多线程执⾏完毕之后才能执⾏主线程的语句。
2 多线程多join的情况下,依次执⾏各线程的join⽅法,前⼀个结束之后,才能执⾏后⼀个。
3 ⽆参数,则等待到该线程结束,才开始执⾏下⼀个线程的join。
4 设置参数后,则等待该线程N秒之后不管该线程是否结束,就开始执⾏后⾯的主进程。
再贴个例⼦体会⼀下
#!/home/oracle/dbapython/bin/python
#encoding=utf-8
import threading
import time
from Queue import Queue
class Producer(threading.Thread):
def run(self):
global queue
count = 0
while True:
for i in range(100):
if queue.qsize() > 1000:
pass
else:
count = count +1
msg = '⽣成产品'+str(count)
queue.put(msg)
print msg
time.sleep(1)
class Consumer(threading.Thread):
def run(self):
global queue
while True:
for i in range(3):
if queue.qsize() < 50:
pass
else:
msg = self.name + '消费了 '+()
print msg
time.sleep(1)
queue = Queue()
def test():
for i in range(100):
msg='初始产品'+str(i)
print msg
queue.put(msg)
for i in range(2):
p = Producer()
p.start()
for i in range(5):
c = Consumer()
c.start()
if__name__ == '__main__':
test()
结果如下:
初始产品0
初始产品1
初始产品2
初始产品3
初始产品4
初始产品5
初始产品6
初始产品7
初始产品8
...
初始产品92
初始产品93
初始产品94
初始产品95
初始产品96
初始产品97
初始产品98
初始产品99
⽣成产品1
⽣成产品2
⽣成产品3
⽣成产品4
⽣成产品1
⽣成产品2
⽣成产品3
⽣成产品4
学python需要什么
⽣成产品5
⽣成产品5
Thread-3消费了初始产品0
⽣成产品6