zoukankan      html  css  js  c++  java
  • 再看python多线程------threading模块

    现在把关于多线程的能想到的需要注意的点记录一下:

    关于threading模块:

    1、关于 传参问题

      如果调用的子线程函数需要传参,要在参数后面加一个“”否则会抛参数异常的错误。

      如下:

    1     for i in xrange(5):
    2         threads.append(threading.Thread(target=worker,args=(i,)))

    2、关于join()阻塞

      join()方法一旦被调用,这个线程就会被阻塞住,等其他线程执行完才执行自身。当我们在主线程A中,创建了n个子线程,这里需要注意,根据需求是应该去阻塞父线程还是子线程,还有就是t.join()放的位置,比较下面例子:

      ---->子线程和父线程都不阻塞

     1 # -*- coding:utf-8 -*-
     2 import Queue,time,threading
     3 start = time.clock()
     4 
     5 def worker(m):
     6     print 'worker',m
     7     time.sleep(1)
     8     return
     9 
    10 if __name__ == "__main__":
    11     threads = []
    12     for i in xrange(5):
    13         threads.append(threading.Thread(target=worker,args=(i,)))
    14     for t in threads:
    15         t.start()
    16         #t.join()  #阻塞子线程
    17 
    18     #t.join()   #阻塞父线程
    19 
    20     end = time.clock()
    21     print "finished: %.3fs" %(end-start)

    得到输入:

    worker 0
    worker 1
    worker 2
    worker 3
    worker 4
    finished: 0.001s

    这里,其实父线程已经结束,因为已经打印出了finished:0.001s,但是子线程并没有执行完,sleep 1秒之后,才出现“Process finished with exit code 0”的程序结束标志。

      

      ----->同样代码,当阻塞子线程时,输出如下:

    worker 0
    worker 1
    worker 2
    worker 3
    worker 4
    finished: 5.004s

    这里,由于5个子线程分别刚被t.start()之后,立即把自身阻塞了,所以它们会按序执行,同样,程序sleep了5秒,一定要注意,你这样做,程序的效率并没有提升,仍然需要5秒的时间。所以这样是有问题的,问题出在t.join()代码放的位置,应该再 for t in threads:  t.join(),使得这些线程同时start,然后同时join()。

      -------> 同样代码,当阻塞父线程时,输出如下:

    worker 0
    worker 1
    worker 2
    worker 3
    worker 4
    finished: 1.003s

    这里,阻塞父线程,父线程会等待子线程结束,才会继续运行打印finished,程序的效率也得到了提升。这相当于上面提到的,先把所有的子线程start了,再join掉。

    3、关于setDaemon()方法

      setDaemon()方法是设置在子线程中的,当我们在父线程A中创建了n个子线程之后,给我们喜欢的子线程设置setDaemon(True)后,当它们的父线程运行结束之后,不管这些子线程运行结束还是没结束,它会直接结束程序。这里,还有一个需要注意的,setDaemon()方法必须设置在start()方法之前,否则会抛RuntimeError异常。

      还用上面的例子:

     1 # -*- coding:utf-8 -*-
     2 import Queue,time,threading
     3 start = time.clock()
     4 
     5 def worker(m):
     6     print 'worker',m
     7     time.sleep(1)
     8     return
     9 
    10 if __name__ == "__main__":
    11     threads = []
    12     for i in xrange(5):
    13         threads.append(threading.Thread(target=worker,args=(i,)))
    14     for t in threads:
    15         t.setDaemon(True)
    16         t.start()
    17         #t.join()  #阻塞子线程
    18 
    19     #t.join()   #阻塞父线程
    20 
    21     end = time.clock()
    22     print "finished: %.3fs" %(end-start)

     这里,没有阻塞父线程,得到的输出如下:

    worker 0
    worker 1
    worker 2
    worker 3
    worker 4
    finished: 0.001s

    说明,主线程一旦结束,会直接把子线程的内存回收,结束整个进程的运行。子进程的sleep 1没有执行就退出了。对于某些辅助子线程的应用场景,这个应该会有用。

    4、创建子线程的两种方式

       第一种是上面提到的,创建子线程要执行的函数(worker),然后把这个函数传递进threading.Thread的对象中,让它来执行;

      第二种是直接从threading.Thread类继承,创建一个新的类,通过重写这个新的类里面的run()方法,实现子线程要执行的内容,例如:

     1 # -*- coding:utf-8 -*-
     2 __author__ = 'webber'
     3 import threading,time
     4 
     5 class Mythread(threading.Thread):
     6 
     7     def __init__(self,m):
     8         threading.Thread.__init__(self)
     9         self.m = m
    10 
    11     def run(self):
    12         print 'worker', self.m
    13         time.sleep(1)
    14         return
    15 
    16 if __name__ == "__main__":
    17     start = time.clock()
    18 
    19     threads = []
    20     for i in xrange(5):
    21         threads.append(Mythread(i))
    22     for t in threads:
    23         t.start()
    24 
    25     t.join()
    26     end = time.clock()
    27     print "finished: %.3fs" % (end - start)

    输出和上面的主线程阻塞的结果一样

    这里要注意一下黄色部分,调用的时候的传参方式。

    5、关于锁----> Lock、RLock、Condition方法

      之前有提到,由于python理论上是无法实现真正意义上的多线程的,即使你有多个CPU,python的多线程也只能利用一个,那么为了防止在多线程中对共享数据空间的数据修改时发生的尴尬,threading模块继承了thread模块的Lock方法,这是最简单的锁,实现也比较简单,只需要在子线程中修改数据前后分别加上锁和释放锁即可。

    就是以下三句话:

      a、主函数中创建一个锁的对象: 例如: lock = threading.Lock()    #返回一个新的Lock对象,创建一把锁。

      b、在子线程需要对数据进行修改之前,lock.acquire()       #获取这把锁

      c、在子线程对数据进行修改之后,  lock.acquire()    #释放这把锁

    下面有个代码应用小例子:

     1 # -*- coding:utf-8 -*-
     2 __author__ = 'webber'
     3 import threading, time, random
     4 
     5 dish = 0
     6 lock = threading.Lock()
     7 
     8 
     9 def producerFunction():
    10     '''如果投的筛子比0.5大,则向盘子中增加一个苹果'''
    11     global lock, dish
    12     while dish < 10:
    13         if (random.random() > 0.5):
    14             lock.acquire()
    15             dish += 1
    16             print('生产者增加了一个苹果,现在有%d个苹果' % (dish,))
    17             lock.release()
    18             time.sleep(random.random() * 3)
    19 
    20 
    21 def consumerFunction():
    22     '''如果投的筛子比0.5小,则从盘子中取一个苹果'''
    23     global lock, dish
    24     while dish > 0:
    25         if (random.random() < 0.5):
    26             lock.acquire()
    27             dish -= 1
    28             print('消费者拿走一个苹果现,现在有%d个苹果' % (dish,))
    29             lock.release()
    30             time.sleep(random.random() * 3)
    31 
    32 
    33 def begin():
    34     ident1 = threading.Thread(target=producerFunction())
    35     ident2 = threading.Thread(target=consumerFunction())
    36     ident1.start()
    37     ident2.start()
    38 
    39 
    40 if __name__ == '__main__':
    41     begin()
    View Code

      其次,threading模块提出了一个更高级的锁RLock,它的出现是为了解决Lock可能会出现的死锁问题,即:当由于疏忽时,可能会出现一个子线程内同一把锁对象连续acquire()两次,那么由于第一次的acquire没有release,那么第二次的acquire请求会把该子线程挂起,导致lock对象永远不会release,造成死锁。而RLock对象允许一个线程多次对其进行acquire操作,在其内部通过counter变量维护着线程acquire的次数,而每一次的acquire操作必须有一个release操作与之对应,在所有的release操作完成之后,别的线程才能申请该RLock对象。使用上暂时我就把它当成Lock方法试了试,通过。~~~

      最后,threading模块提供了更高级的封装,算是一种高级的多线程间同步方式,包括threading.Event和threading.Condition,其中,threading.Event为简单的同步方式,一个进程标记为event,其他的进程就需要等待,用到下面几种方法:

    Event.wait([timeout]) 阻塞线程,直到Event对象内部标识位被设置为True或超时(如果提供了参数timeout)
    Event.set() 将标识号设为True
    Event.clear() 设为标识符False

     

      threading.Condition 可以把Condition理解为更高级的锁,它提供了比RLock更高级的功能,允许我们能够控制复杂的线程同步问题,它在内部维护了一个锁对象(默认为RLock),可以在创建Condition对象的时候把锁对象作为参数传入。Condition也提供了acquire和release方法,它的特色在于内部的wait和notify机制,具体可看threading模块,下面的方法只有在对象获取到锁之后才能调用,否则,将会抛RuntimeError异常

    Condition.wait([timeout]):  wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。 
    Condition.notify() 唤醒一个挂起的线程(如果存在挂起的线程)。注意:notify()方法不会释放所占用的琐。
    Condition.notify_all() 唤醒所有挂起的线程(如果存在挂起的线程)。注意:这些方法不会释放所占用的琐。

    参考:http://orangeholic.iteye.com/blog/1720421

    6、其他方法

      由于threading是继承的thread模块的,所以还有些公共属性方法,比如:

    t.getName():获取子线程的名称,默认为:Tread-n (n为线程序列号)

    t.setName():设置子线程的名称

    t.ident:获取线程标识符,要在t.start()之后调用才有效,否则返回None

    t.is_alive():判断子线程是否激活,返回True或False

    关于Semaphore、event、Condition的具体实例,没再去尝试,以后遇到再试,可参考这篇博客:

        http://www.jb51.net/article/57672.htm

  • 相关阅读:
    JerryScript:物联网开发者的得力工具
    使用 scipy.fft 进行Fourier Transform:Python 信号处理
    解析WeNet云端推理部署代码
    华为云消息队列服务荣获首个双擎可信云稳定性最高级认证
    .NET从互联网上获取当前时间并更新系统时间
    豆瓣电台WP7客户端 开发记录1
    HTML格式化为标准XML
    豆瓣电台WP7客户端 开发记录6
    豆瓣电台 for WP7 客户端开源
    豆瓣电台WP7客户端 开发记录7
  • 原文地址:https://www.cnblogs.com/webber1992/p/5970669.html
Copyright © 2011-2022 走看看