• python进阶学习笔记(四)--多线程thread


     

    在使用多线程之前,我们首页要理解什么是进程和线程。

    什么是进程?

    计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。

    什么是线程?

    线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。我们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。

     

     7.2.1、单线程

      在单线程中顺序执行两个循环。一定要一个循环结束后,另一个才能开始。总时间是各个循环运行时间之和。 

    onetherad.py

    from time import sleep, ctime 
    
    def loop0():
        print 'start loop 0 at:', ctime()
        sleep(4)
        print 'loop 0 done at:', ctime() 
     
    def loop1():
        print 'start loop 1 at:', ctime()
        sleep(2)
        print 'loop 1 done at:', ctime() 
     
    def main():
        print 'start:', ctime()
        loop0() 
        loop1()
        print 'all end:', ctime() 
        
    if __name__ == '__main__':
        main()

    运行结果:

    start loop 0 at: Mon Dec 23 09:59:44 2013
    loop 0 done at: Mon Dec 23 09:59:48 2013
    start loop 1 at: Mon Dec 23 09:59:48 2013
    loop 1 done at: Mon Dec 23 09:59:50 2013
    all end: Mon Dec 23 09:59:50 2013

      Python通过两个标准库threadthreading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。threading基于Java的线程模型设计。锁(Lock)和条件变量(Condition)在Java中是对象的基本行为(每一个对象都自带了锁和条件变量),而在Python中则是独立的对象。

     

     

     7.2.1、thread模块

     

    mtsleep1.py

    import thread 
    from time import sleep, ctime 
    loops = [4,2]
    def loop0():
        print 'start loop 0 at:', ctime()
        sleep(4)
        print 'loop 0 done at:', ctime() 
     
    def loop1(): 
        print 'start loop 1 at:', ctime() 
        sleep(2) 
        print 'loop 1 done at:', ctime()
    
    def main(): 
        print 'start:', ctime() 
        thread.start_new_thread(loop0, ()) 
        thread.start_new_thread(loop1, ()) 
        sleep(6)
        print 'all end:', ctime() 
    
    if __name__ == '__main__':
        main() 

      start_new_thread()要求一定要有前两个参数。所以,就算我们想要运行的函数不要参数,我们也要传一个空的元组。 

      这个程序的输出与之前的输出大不相同,之前是运行了 6秒,而现在则是 秒,是最长的循环的运行时间与其它的代码的时间总和。

    运行结果:

    start: Mon Dec 23 10:05:09 2013
    start loop 0 at: Mon Dec 23 10:05:09 2013
    start loop 1 at: Mon Dec 23 10:05:09 2013
    loop 1 done at: Mon Dec 23 10:05:11 2013
    loop 0 done at: Mon Dec 23 10:05:13 2013
    all end: Mon Dec 23 10:05:15 2013

       睡眠 秒和 秒的代码现在是并发执行的。这样,就使得总的运行时间被缩短了。你可以看到,loop1 甚至在 loop0 前面就结束了。

      程序的一大不同之处就是多了一个“sleep(6)”的函数调用。如果我们没有让主线程停下来,那主线程就会运行下一条语句,显示“all end”,然后就关闭运行着 loop0()和 loop1()的两个线程并退出了。我们使用 秒是因为我们已经知道,两个线程(你知道,一个要 秒,一个要 秒)在主线程等待 秒后应该已经结束了。

      你也许在想,应该有什么好的管理线程的方法,而不是在主线程里做一个额外的延时 秒的操作。因为这样一来,我们的总的运行时间并不比单线程的版本来得少。而且,像这样使用 sleep()函数做线程的同步操作是不可靠的。如果我们的循环的执行时间不能事先确定的话,那怎么办呢?这可能造成主线程过早或过晚退出。这就是锁的用武之地了。

     

    mtsleep2.py

    #coding=utf-8
    import thread 
    from time import sleep, ctime 
     
    loops = [4,2] 
     
    def loop(nloop, nsec, lock):
        print 'start loop', nloop, 'at:', ctime() 
        sleep(nsec) 
        print 'loop', nloop, 'done at:', ctime()
        #解锁
        lock.release() 
     
    def main():
        print 'starting at:', ctime()
        locks =[]
        #以loops数组创建列表,并赋值给nloops
        nloops = range(len(loops)) 
             
        for i in nloops:
            lock = thread.allocate_lock()
            #锁定
            lock.acquire()
            #追加到locks[]数组中 
            locks.append(lock)
    
        #执行多线程
        for i in nloops:
            thread.start_new_thread(loop,(i,loops[i],locks[i]))
            
        for i in nloops:
            while locks[i].locked():
                pass
    
        print 'all end:', ctime() 
    
    if __name__ == '__main__': 
        main()

    thread.allocate_lock() 

      返回一个新的锁定对象。

    acquire() /release() 

      一个原始的锁有两种状态,锁定与解锁,分别对应acquire()release() 方法。

    range()

      range()函数来创建列表包含算术级数。

    range(len(loops))理解:

    >>> aa= "hello"
    
    #长度计算
    >>> len(aa)
    5
    
    #创建列表
    >>> range(len(aa))
    [0, 1, 2, 3, 4]
    
    #循环输出列表元素
    >>> for a in range(len(aa)):
        print a
    
        
    0
    1
    2
    3
    4

      我们先调用 thread.allocate_lock()函数创建一个锁的列表,并分别调用各个锁的 acquire()函数获得锁。获得锁表示“把锁锁上”。锁上后,我们就把锁放到锁列表 locks 中。

      下一个循环创建线程,每个线程都用各自的循环号,睡眠时间和锁为参数去调用 loop()函数。为什么我们不在创建锁的循环里创建线程呢?有以下几个原因:(1) 我们想到实现线程的同步,所以要让“所有的马同时冲出栅栏”。(2) 获取锁要花一些时间,如果你的线程退出得“太快”,可能会导致还没有获得锁,线程就已经结束了的情况。

      在线程结束的时候,线程要自己去做解锁操作。最后一个循环只是坐在那一直等(达到暂停主线程的目的),直到两个锁都被解锁为止才继续运行。

    mtsleep2.py运行结果:

    starting at: Mon Dec 23 20:57:26 2013
    start loop start loop0  1at:  at:Mon Dec 23 20:57:26 2013 
    Mon Dec 23 20:57:26 2013
    loop 1 done at: Mon Dec 23 20:57:28 2013
    loop 0 done at: Mon Dec 23 20:57:30 2013
    all end: Mon Dec 23 20:57:30 2013

     

     

     7.2.1、threading模块

      我们应该避免使用thread模块,原因是它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。有时我们并不期望这种行为,这时就引入了守护线程的概念。threading模块则支持守护线程。

    mtsleep3.py

    #coding=utf-8
    import threading 
    from time import sleep, ctime 
     
    loops = [4,2] 
     
    def loop(nloop, nsec):
        print 'start loop', nloop, 'at:', ctime() 
        sleep(nsec) 
        print 'loop', nloop, 'done at:', ctime()
        
     
    def main():
        print 'starting at:', ctime()
        threads = []
        nloops = range(len(loops)) 
            
        #创建线程
        for i in nloops:
            t = threading.Thread(target=loop,args=(i,loops[i]))
            threads.append(t)
    
        #开始线程
        for i in nloops:
            threads[i].start()
    
        #等待所有结束线程
        for i in nloops:
            threads[i].join()
    
    
        print 'all end:', ctime() 
    
    if __name__ == '__main__': 
        main()

    运行结果:

    starting at: Mon Dec 23 22:58:55 2013
    start loop 0 at: Mon Dec 23 22:58:55 2013
    start loop 1 at: Mon Dec 23 22:58:55 2013
    loop 1 done at: Mon Dec 23 22:58:57 2013
    loop 0 done at: Mon Dec 23 22:58:59 2013
    all end: Mon Dec 23 22:58:59 2013

    start()

      开始线程活动

    join()

      等待线程终止

     

      所有的线程都创建了之后,再一起调用 start()函数启动,而不是创建一个启动一个。而且,不用再管理一堆锁(分配锁,获得锁,释放锁,检查锁的状态等),只要简单地对每个线程调用 join()函数就可以了。

    join()会等到线程结束,或者在给了 timeout 参数的时候,等到超时为止。join()的另一个比较重要的方面是它可以完全不用调用。一旦线程启动后,就会一直运行,直到线程的函数结束,退出为止。

     

    使用可调用的类

     mtsleep4.py

    #coding=utf-8
    import threading 
    from time import sleep, ctime 
     
    loops = [4,2] 
    
    class ThreadFunc(object):
    
        def __init__(self,func,args,name=''):
            self.name=name
            self.func=func
            self.args=args
    
        def __call__(self):
            apply(self.func,self.args)
    
    def loop(nloop,nsec):
        print "seart loop",nloop,'at:',ctime()
        sleep(nsec)
        print 'loop',nloop,'done at:',ctime()
    
    def main():
        print 'starting at:',ctime()
        threads=[]
        nloops = range(len(loops))
    
        for i in nloops:
            #调用ThreadFunc实例化的对象,创建所有线程
            t = threading.Thread(
                target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) 
            threads.append(t)
            
        #开始线程
        for i in nloops:
            threads[i].start()
    
        #等待所有结束线程
        for i in nloops:
            threads[i].join()
    
        print 'all end:', ctime() 
    
    if __name__ == '__main__': 
        main()

     运行结果:

    starting at: Tue Dec 24 16:39:16 2013
    seart loop 0 at: Tue Dec 24 16:39:16 2013
    seart loop 1 at: Tue Dec 24 16:39:16 2013
    loop 1 done at: Tue Dec 24 16:39:18 2013
    loop 0 done at: Tue Dec 24 16:39:20 2013
    all end: Tue Dec 24 16:39:20 2013

    创建新线程的时候,Thread 对象会调用我们的ThreadFunc 对象,这时会用到一个特殊函数__call__()。由于我们已经有了要用的参数,所以就不用再传到 Thread()的构造函数中。由于我们有一个参数的元组,这时要在代码中使用 apply()函数。

    我们传了一个可调用的类(的实例),而不是仅传一个函数。

    __init__()

    方法在类的一个对象被建立时运行。这个方法可以用来对你的对象做一些初始化

    apply()

    apply(func [, args [, kwargs ]]) 函数用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,kwargs是一个包含关键字参数的字典。

    apply() 用法:

    #不带参数的方法
    >>> def say():
        print 'say in'
    
    >>> apply(say)
    say in
    
    #函数只带元组的参数
    >>> def say(a,b):
        print a,b
    
    >>> apply(say,('hello','虫师'))
    hello 虫师
    
    #函数带关键字参数
    >>> def say(a=1,b=2):
        print a,b
    
        
    >>> def haha(**kw):
        apply(say,(),kw)
    
        
    >>> haha(a='a',b='b')
    a b
  • 相关阅读:
    JVM内存分析 规格严格
    比MyBatis快了100倍 规格严格
    jstatd运行,远端监控 规格严格
    浅谈Linux下查看某一进程占用的内存 规格严格
    如何查看jvm堆外内存使用情况? 规格严格
    nginx location中uri的截取的实现方法 规格严格
    date获取毫秒和秒 规格严格
    DAX 第四篇:行上下文
    钓鱼邮件
    Htmx 使html 也可以发送请求
  • 原文地址:https://www.cnblogs.com/fnng/p/3489321.html
Copyright © 2020-2023  润新知