Python 入门 —— Python threading多线程模块

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()

执行如下:
这里写图片描述
控制台显示如下:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/Buster_ZR/article/details/81218252