• 2016/1/2 Python中的多线程(1):线程初探


    ---恢复内容开始---

    新年第一篇,继续Python。


    先来简单介绍线程和进程。

      计算机刚开始发展的时候,程序都是从头到尾独占式地使用所有的内存和硬件资源,每个计算机只能同时跑一个程序。后来引进了一些机制来改进这种调用方法,包括流水线,多进程。我们开始并发执行程序,每个程序交替地被处理器调用,再极高的频率下,你会认为这些程序是在同时执行的,这也就是并发技术。用操作系统来管理并发,将程序读到内存中,然后被操作系统调用开始,它的生命周期就开始了。而每个程序的执行,都是用进程的方式执行,每个进程有自己的地址空间、内存、数据栈及别的什么东西。对于任何一个以进程方式执行的程序,都好似独占了全部的硬件资源。每个进程都有从地址0开始的虚拟内存地址。也就是说,进程是一定意义的抽象。然后操作系统管理所有的进程,给它们分配分时运行的时间。进程之间通过一定的进程间交互手段来通信,不能直接共享信息。

      那什么是线程?

      线程常常被称为轻量级进程,跟进程有些相似,不同的是所有的线程都运行在同一个进程中,共享同样的运行环境,有同样的地址空间、数据栈空间。可以认为在一个进程里,有很多并行执行的线程。

      线程有开始,顺序执行和结束三个部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占或者暂时挂起,让其他线程运行,这种方式叫做让步。

      线程间由于共享了同样的数据空间,所以可以很方便地共享数据和通讯,但是,有一个问题是多个线程同时访问同一片数据,由于顺序不同,可能数据结果会不一致,产生了所谓的竞态条件。

      总的来说,线程是在一个程序里设置的几个并发运行的过程,让几件事情可以同时执行。


    Python中使用线程

      Python的代码由Python虚拟机控制,可以通过全局解释器锁GIL来控制,也可以直接用一些模块来实现我们的需求。接下来主要来介绍thread和threading模块。thread模块一般不推荐使用,因为它在主线程退出时,其他线程若没有结束,还没有清除就退出了,而threading模块确保所有的重要的子线程都退出后才会结束进程。

      默认情况下,Python对线程的支持是打开的。在交互模式下尝试导入thread模块没有错误就表示可用。

    >>> import thread
    >>>

    如果出现了导入错误,那么应该重新编译Python解释器才能运行,这里不作说明。


    先来看一个没有多线程支持例子:

    这里用了time模块的sleep()函数,里面输入一个浮点的参数,表示睡眠的秒数,意味着程序将被挂起这段时间。

    from time import sleep, ctime
    
    def loop0():
        print 'start loop 0 at: %s' % ctime()
        sleep(4)
        print 'loop 0 done at: %s' % ctime()
    
    def loop1():
        print 'start loop 1 at: %s' % ctime()
        sleep(2)
        print 'loop 1 done at: %s' % ctime()
    
    def main():
        print 'starting at: %s' % ctime()
        loop0()
        loop1()
        print 'all Done at: %s' % ctime()
    
    if __name__ == '__main__':
        main()
    >>> 
    starting at: Sat Jan 02 21:17:48 2016
    start loop 0 at: Sat Jan 02 21:17:48 2016
    loop 0 done at: Sat Jan 02 21:17:52 2016
    start loop 1 at: Sat Jan 02 21:17:52 2016
    loop 1 done at: Sat Jan 02 21:17:54 2016
    all Done at: Sat Jan 02 21:17:54 2016

    可以看到,程序毫无疑问的顺序执行了,但是,我们用sleep()挂起的时间并没有意义了。

    所以,让我们看一下用了线程之后的方法:

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

    结果会是这样的

    >>> 
    starting at: Sat Jan 02 21:23:58 2016
    start loop 0 at: Sat Jan 02 21:23:58 2016
    start loop 1 at: Sat Jan 02 21:23:58 2016
    loop 1 done at: Sat Jan 02 21:24:00 2016
    loop 0 done at: Sat Jan 02 21:24:02 2016
    all Done at: Sat Jan 02 21:24:04 2016

    可以看到,这一次loop1和loop0在程序开始后4秒就结束了,只是我们多了一句sleep(6),让整个程序最后还是跑了6秒,加这一句,是防止主线程结束后子线程也就退出了,导致根本没有执行完,但是这种方法实在是很愚蠢,我们最后程序还是跑了6秒才能结束,如果我们有一次并不知道子进程什么时候结束,比如说从键盘读到一条命令后结束,那么该如何写这样的语句呢。接下来我会介绍这种方法,我们先来看看thread这个模块在此处干了什么。

      我们调用了thread的一个方法start_new_thread(funciton, args kwargs=None),这个方法的作用是产生一个新线程,在新线程中用指定的参数和可选的kwargs来调用这个函数。这是一个很简单的线程机制。                                                                                                                                                                                                                                                                                                                                                                                                                       

      接下来用锁来杜绝在主线程中使用sleep()函数:

    import thread
    from time import ctime, sleep
    
    loops = [4, 2]
    
    def loop(nloop, nsec, lock):
        print 'start loop%s at: %s
    ' % (nloop, ctime()),
        sleep(nsec)
        print 'loop%s done at: %s
    ' % (nloop, ctime()),
        lock.release()
    
    def main():
        print 'starting at: %s
    ' % ctime(),
        locks = []
        nloops = range(len(loops))
    
        for i in nloops:
            lock = thread.allocate_lock()
            lock.acquire()
            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 DONE at: %s
    ' %ctime(),
    
    if __name__ == '__main__':
        main()

    显示结果是这样的:

    >>> 
    starting at: Sat Jan 02 21:55:30 2016
    start loop0 at: Sat Jan 02 21:55:30 2016
    start loop1 at: Sat Jan 02 21:55:30 2016
    loop1 done at: Sat Jan 02 21:55:32 2016
    loop0 done at: Sat Jan 02 21:55:34 2016
    all DONE at: Sat Jan 02 21:55:34 2016

    大家可以看到我用了一种很抽风的方式使用print语句,至于为什么不用基本的方法,各位可以看一下用原来的方法输出结果是怎样的。

    这里面,我们用了用了thread.allocate_lock()函数来创建一个锁对象,然后将它存到一个锁的列表里去,每次都得调用acquire()函数来获得锁,也就是把锁锁上,锁上后,通过一个锁的列表,在循环里,每个线程分配到自己的锁,然后一起执行。在线程结束的时候,我们需要解锁。

    为什么不在创建锁的过程中创建进程呢?有两个原因,一个是我们希望每个线程都是同步开始的,要让它们几乎同时开始。另一个是每次获取锁会花一定的时间,如果线程退出的太快,可能锁还没有获取,线程就结束了。

    所以我们需要分配锁,获得锁,释放锁,来实现进程的同步。

    今天先写到这里,下一次再说明threading的使用,那时候,将不需要考虑这些锁的问题。

  • 相关阅读:
    ssh实现免密码登录和文件传输
    linux后台执行程序相关命令
    orchestrator
    curl下载安装与使用
    goland使用
    mysql集群
    consul理解
    my.cnf
    数据库的表设计
    项目常见面试问题
  • 原文地址:https://www.cnblogs.com/SRL-Southern/p/5095386.html
Copyright © 2020-2023  润新知