Python threading多线程模块
- Thread 线程
线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元)
它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程
每条线程并行执行不同的任务
(线程是进程中的一个实体,是被系统独立调度和分派的基本单元)
每一个进程启动时都会最先产生一个线程,即主线程
然后主线程会再创建其他的子线程
1. Thread 类
- Thread是线程类直接传入要运行的方法
介绍一个python内置函数 xrange
- range返回的是一个包含所有元素的列表,xrange返回的是一个生成器,生成器是一个可迭代对象,在对生成器进行迭代时,元素是逐个被创建的。一般来看,在对大序列进行迭代的时候,因为xrange的特性,所以它会比较节约内存。
Thread 示例:
import threading
import time
# 将要执行的方法作为参数传给Thread的构造方法
def action(arg):
time.sleep(1)
print 'the arg is:%s\r' %arg
for i in xrange(4):
t =threading.Thread(target=action,args=(i,))
t.start()
print 'main thread end!'
执行如下:
控制台显示如下:
按照python解释器的执行来看,’man thread end!’ 应该是再最后执行,但由于添加线程的原因
,线程字线程的执行和主线程的执行是并发的
这样看可能不清晰,我们可以再来一个例子
# 调用threading模块
import threading
from time import ctime, sleep
def music(a):
for i in range(2):
print 'I was listening to %s. %s' % (a,ctime())
sleep(1)
def movie(b):
for i in range(2):
print 'I was watching to %s. %s' % (b,ctime())
sleep(5)
# 建立线程
t1 = threading.Thread(target=music,args=('告白气球',))
# 启动
t1.start()
t2 = threading.Thread(target=movie,args=('泰坦尼克号',))
t2.start()
print 'all over %s' % ctime()
执行如下:
控制台显示如下:
由控制台返回的结果可看出,这几个线程是并行的
Thread 类中的方法:
构造方法
- Thread(group=None, target=None, name=None, args=(), kwargs={})
- group: 线程组,目前还没有实现,库引用中提示必须是None;
- target: 要执行的方法;
- name: 线程名;
- args/kwargs: 要传入方法的参数。
实例方法:
- get/setName(name): 获取/设置线程名。
- is/setDaemon(bool):获取/设置是后台线程(默认前台线程(False))。(在start之前设置)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,主线程和后台线程均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止- start():启动线程。
- join([timeout]):阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)。
getName 获取线程名称
from threading import Thread
def Foo(arg):
print arg
print 'before'
t1 = Thread(target=Foo,args=(1,))
t1.start()
print t1.getName() # 显示t1所代表的线程的名称
t2 = Thread(target=Foo,args=(2,))
t2.start()
print t2.getName() # 显示t2所代表的线程的名称
print 'after'
执行如下:
控制台显示如下:
setDaemon(bool) 设置守护线程与用户线程
设定守护线程
from threading import Thread
import time
def Test():
for item in range(100):
print item
time.sleep(1)
print 'before'
t1 = Thread(target=Test)
# 为True表示t1为守护线程
# 主线程结束后,t1不管是否执行完都会结束
t1.setDaemon(True)
t1.start()
print 'after'
time.sleep(10) # 延迟10s结束进程
执行如下:
控制台显示如下:主线程延时10秒结束,后无论字线程是否完成,都结束
设定用户线程
from threading import Thread
import time
def Test():
for item in range(100):
print item
time.sleep(1)
print 'before'
t1 = Thread(target=Test)
# 为False表示t1为用户线程
# 主线程结束后,t1还会继续执行
t1.setDaemon(False)
t1.start()
print 'after'
time.sleep(10) # 延迟10s结束进程
print 'end'
执行如下:
控制台显示如下:
主线程结束,字线程仍旧执行
join 方法,阻塞主线程
调用.join()方法后,子线程正常运行,父线程会等待子线程结束后再继续运行
from threading import Thread
import time
def test():
for item in range(10):
print item
time.sleep(1)
print 'start'
t1 = Thread(target=test)
t1.start()
# 主线程到join()就不往下走了,直到子线程执行完
t1.join()
print 'end'
执行如下:
控制台显示如下:
若是再join()方法中加入时间,则表示阻塞父线程的时间,时间一到父线程结束
如给join中加上数字5 执行结果如下:
2 生产者消费者模型
- 多线程能干什么:
生产者消费者问题:(经典)
一直生产 一直消费 中间有阀值 避免供求关系不平衡- 生产者消费者的优点(为什么经典的设计模式)
1.解耦(让程序各模块之间的关联性降到最低)
假设生产者和消费者是两个类,如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合),
如果将来消费者的代码发生变换,可能会影响到生产者,而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了- 生活中的例子:我们 邮筒 邮递员
举个例子,我们去邮局投递信件,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员,有人会说,直接交给邮递员不是挺简单的嘛,其实不简单,你必须得认识邮递员,才能把信给他(光凭身上的制服,万一有人假冒呢???),
这就产成你和邮递员之间的依赖了(相当于生产者消费者强耦合),万一哪天邮递员换人了,
你还要重新认识一下(相当于消费者变化导致修改生产者代码),而邮筒相对来说比较固定,
你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)- 2.支持并发
生产者消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者之需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者者只需要从缓冲区里拿数据即可,这样就不会因为彼此速度而发生阻塞接着上面的例子:如果我们不使用邮筒,我们就得在邮局等邮递员,直到他回来了,我们才能把信给他,这期间我们啥也不能干(也就是产生阻塞),或者邮递员挨家挨户的问(产生论寻)- 3.支持忙闲不均如果制造数据的速度时快时慢,缓冲区的好处就体现出来了,当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中,等生产者的速度慢下来,消费者再慢慢处理情人节信件太多了,邮递员一次处理不了,可以放在邮筒中,下次再来取
加一点queue模块的知识
- queue——同步的队列类
queue模块实现了多生产者,多消费者的队列。当 要求信息必须在多线程间安全交换,这个模块在 线程编程时非常有用 。Queue模块实现了所有要求的锁机制。 说了半天就是Queue模块主要是多线程,保证线程安全使用的。
这个类实现了三种类型的queue,区别仅仅在于进去和取出的位置。在一个FIFO(First In,First Out)队列中,先加先取。在一个LIFO(Last In First Out)的队列中,最后加的先出来(操作起来跟stack一样)。priority队列,有序保存,优先级最低的先出来。
内部实现是在抢占式线程加上临时锁。但是没有涉及如何去处理线程的重入。- 类:class queue.Queue(maxsize = 0)
构造一个FIFO队列,maxsize可以限制队列的大小。如果队列的大小达到了队列的上限,就会加锁,加入就会阻塞,直到队列的内容被消费掉。maxsize的值小于等于0,那么队列的尺寸就是无限制的- 队列对象的方法:
- Queue.qsize():返回queue的近似值。注意:qsize>0不保证(get)取元素不阻塞。qsize< maxsize不保证(put)存元素不会阻塞
- Queue.put(item, block=True, timeout=None): 往队列里放数据。如果满了的话,blocking = False 直接报 Full异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能存入,报Full异常。
- Queue.put_nowait(item):往队列里存放元素,不等待
- Queue.get(item, block=True, timeout=None): 从队列里取数据。如果为空的话,blocking = False 直接报 empty异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能读取,报empty异常。
- Queue.get_nowait(item):从队列里取元素,不等待
两个方法跟踪入队的任务是否被消费者daemon进程完全消费
简单的数据存取
示例:
import threading
# Queue
import Queue
import time
import random
def producer(name,que):
# 定义生产者模型
while True:
que.put('baozi')
# 往队列中放数据
print '%s made a baozi..' % name
time.sleep(random.randrange(5))
# 调用随机数列,调用的时间为5内的随机数
def consumer(name,que):
while True:
que.get()
print "%s Got a baozi" % name
time.sleep(random.randrange(3))
q = Queue.Queue()
p1 = threading.Thread(target=producer,args=['chef1', q])
p2 = threading.Thread(target=producer,args=['chef2', q])
p1.start()
p2.start()
c1 = threading.Thread(target=consumer,args=['tom', q])
c2 = threading.Thread(target=consumer,args=['jarry', q])
c1.start()
c2.start()
执行如下:
控制台显示如下:
加上返回队列大小的限制,将会返回异常,然后通过异常捕获进行处理
示例:
import threading
import Queue
import time
import random
def producer(name, que):
while True:
if que.qsize() < 3:
que.put('')
print '%s Made a baozi.' % name
else:
print '还有三个包子'
time.sleep (random.randrange(5))
def consumer(name, que):
while True:
try:
# 加上异常捕获
que.get_nowait()
print '%s:Got a baozi..' % name
except Exception:
print '没有包子了'
time.sleep(random.randrange(3))
# 创建队列
q = Queue.Queue()
p1 = threading.Thread(target=producer,args=['chef1',q])
p2 = threading.Thread(target=producer,args=['chef2',q])
p1.start()
p2.start()
c1 = threading.Thread(target=consumer,args=['tom',q])
c2 = threading.Thread(target=consumer,args=['harry',q])
c1.start()
c2.start()
执行如下:
控制台显示如下:
3 lock
- 由于线程之间随机调度:某线程可能在执行n条后,CPU接着执行其他线程。为了多个线程同时操作一个内存中的资源时不产生混乱,我们使用锁。
Lock(指令锁)是可用的最低级的同步指令。Lock处于锁定状态时,不被特定的线程拥有。Lock包含两种状态——锁定和非锁定,以及两个基本的方法。
可以认为Lock有一个锁定池,当线程请求锁定时,将线程至于池中,直到获得锁定后出池。池中的线程处于状态图中的同步阻塞状态。- 实例方法:
- acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。
- release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。
示例:未加线程锁,资源混乱
import threading
import time
num = 0
def run(n):
time.sleep(1)
global num
num += 1
print '%s\n ' % num
for i in range(1500):
t = threading.Thread(target=run, args=(i,))
t.start()
执行如下:
控制台显示如下:要求直到1500,但值显示到1461
加线程锁
def run(n):
time.sleep(1)
global num
# 加线程锁
lock.acquire()
num += 1
print '%s\n ' % num
# 释放线程锁
lock.release()
lock = threading.Lock()
for i in range(1500):
t = threading.Thread(target=run, args=(i,))
t.start()
执行如下:
控制台显示如下: