精华内容
下载资源
问答
  • python多线程编程实现网络串口透传, 为TCP客户端网口数据串口透传。
  • Python多线程编程

    2018-07-04 09:22:15
    Python多线程编程文档说明 多进程编程 一、 multiprocessing 模块 1. multiprocessing 模块提供了多进程编程的能力 它的API非常类似于 threading 模块,但是也提供了一些threading 模块不具有的能力 相比于线程,它...
  • 本文实例讲述了Python多线程编程之多线程加锁操作。分享给大家供大家参考,具体如下: Python语言本身是支持多线程的,不像PHP语言。 下面的例子是多个线程做同一批任务,任务总是有task_num个,每次线程做一个任务...
  • 主要介绍了Python多线程编程(三):threading.Thread类的重要函数和方法,本文讲解了线程名称、join方法、setDaemon方法等内容,需要的朋友可以参考下
  • Python多线程中如何创建一个线程对象如果你要创建一个线程对象,很简单,只要你的类继承threading.Thread,然后在__init__里首先调用threading.Thread的__init__方法即可 复制代码 代码如下:import threading ...
  • 前一篇文章Python:使用threading模块实现多线程编程四[使用Lock互斥锁]我们已经开始涉及到如何使用互斥锁来保护我们的公共资源了,现在考虑下面的情况– 如果有多个公共资源,在线程间共享多个资源的时候,如果两个...
  • python多线程编程

    千次阅读 2016-05-22 13:27:18
    1、多线程的发展背景 随着计算机的发展,无论是硬件还是软件都在快速的发展。 在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,...

    1、多线程的发展背景

    随着计算机的发展,无论是硬件还是软件都在快速的发展。

    在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,所有的进程都是排成一个队列,然后cpu取出其中的一个进程,然后运行。

    在硬件发展的时候,慢慢发展为几颗cpu,并且发展出来了几核cpu,从而在一般的服务器上都是四核的,并且至少是两颗,从而在每次服务器进行处理的时候,都是进行多进程的处理,可能说一颗cpu处理一个进程的情况出现,从而在这个时候,程序也就发展了多进程多线程的概念,在一个进程中有多个线程,而一个程序又可以运行多个线程。

     

    在多进程和多线程主要发展出来的目标,也就是提高计算的性能,从而在这里的编程就称之为多线程编程,multi threading。

     

    2、 python中的多线程

    在python中,多线程是存在的,但是和其他编程中的多线程是不一样的,也就是在一个进程中存在着多个线程,但是,很遗憾的是,在python中仅仅支持一个线程的运行,也就是说,在python中的多线程依旧像是单线程的运行。

    在python中存在一个锁,也就是GIL,global interpreter lock,全局解释器锁,用来控制在同一时刻只有仅仅一个线程的运行。在python中,多线程的执行方式如下:

    看到上面这种情况,也就是在一个进程里的确是可以有多个线程,但是同一时刻还是只有一个线程的运行,那么为什么还要使用多线程呢,用一个线程不就好了?

     

    在python中,多线程的最好的处理的问题是对I/O密集型的操作,也就是说,在计算密集型的操作的时候,是不适合使用多线程的,因为在同一时刻,还是只有一个线程使用到cpu,但是I/O是阻塞的方式,从而python的多线程还是用在I/O操作上面,例如有大量用户的输入等IO操作。

     

    如果是计算密集型的操作,那么在python中,更适合使用多进程的方式,在python中,使用多进程的时候,没有像线程锁的这种限制。

     

    3、 进程和线程的区别

    进程的英文表示为process,线程的英文表示为thread。

    进程消耗的资源(主要是内存)比线程需要消耗的资源要多,进程具有自己独立的数据空间;而在线程中,线程都是存在于一个进程当中,从而所有的线程都是共享资源的,线程之间的数据通信很简单,而进程间的通信需要使用到IPC,inter process comminication,可能就要使用socket进行通信,线程之间的通信,可以使用一个数据结构的容器就可以进行通信,例如数据结构Queue,这个是一个先进先出的数据结构,可以用来线程之间的通信。

     

    在运行程序的时候,操作系统管理进程,进程中存在线程,其实是进程管理线程,如果具有多个线程,那么主线程就会管理子线程,在运行程序的时候,最开始就是一个进程一个主线程,主线程会管理子线程。

     

    在操作系统中,我们可以直接查询到进程号,也就是进程的PID,可以直接杀死一个进程,但是我们不能直接杀死一个线程,查询进程的id如下所示:

     

    [root@python 521]# ps -ef|grep server |grep -v grep
    root     10981  1677  0 00:28 pts/0    00:00:00 python server.py


    在其中pid为10981,从而可以使用kill命令直接杀死进程,然而并不能查到线程的pid,也就不能杀死一个线程。

     

     

    线程的结束,要么是线程自己运行结束,要么就是主线程杀死子线程。

     

    在线程中还有一个就是守护线程,守护线程,也就是在后台运行,主线程不会等待守护线程。使用守护进程的好处就是,主线程不用理会守护线程,如果主线程退出了,那么守护线程也就自动销毁了,守护线程的好处就是主线程不需要花时间来管理。

     

    4、 python中的多线程模块介绍

    在python中,多线程的模块一个是thread模块,还有一个是threading模块,推荐使用threading模块,因为threading属于高级模块,进行了大量的封装,东西总是越高级越好嘛。其实主要原因是,在thread模块中,你需要自己来进行管理子线程和主线程之间同步的问题,而在threading模块中,已经进行了相关的封装,从而不需要管同步的问题。

     

    5、 多线程的例子

    在使用python的多线程的时候,基本步骤如下:

    直接调用函数的多线程如下:

     

    #!/usr/bin/env python
    
    from threading import Thread
    import time
    
    def loop(name,seconds):
        print 'start loop',name,' at:',time.ctime()
        time.sleep(1)
        print 'end loop',name,' at:',time.ctime()
        
    if __name__ == '__main__':
        loops = [2,4]
        nloops = range(len(loops))
        threads = []
        print 'starting at :',time.ctime()
        for i in nloops:
            t = 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 DONE at :',time.ctime()


    在这里,主要要做的操作在loop函数中,从而创建多个线程来执行loop函数,在创建线程对象Thread的时候,直接使用的是调用函数的方法,也就是创建线程的第一种方法,直接调用我们要进行操作的函数。

     

    第二种方法使用的是可调用的类对象:

     

     

    #!/usr/bin/env python
    
    from threading import Thread
    import time
    
    def loop(name,seconds):
        print 'start loop',name,' at:',time.ctime()
        time.sleep(1)
        print 'end loop',name,' at:',time.ctime()
        
    class ThreadFunc(object):
        def __init__(self,func,args,name=''):
            self.name = name
            self.func = func
            self.args = args
    
        def __call__(self):
            self.func(*self.args)
    
    if __name__ == '__main__':
        loops = [2,4]
        nloops = range(len(loops))
        threads = []
        print 'starting at :',time.ctime()
        for i in nloops:
            t = 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 DONE at :',time.ctime()

     

     

    在使用可调用类对象的时候,主要就是在类的定义中,需要定义个特殊的方法__call__,然后在创建Thread的时候,需要使用直接使用target为可调用对象的实例化。

     

    第三种方法主要就是子类化,代码如下:

     

    #!/usr/bin/env python
    
    from threading import Thread
    import time
    
    def loop(name,seconds):
        print 'start loop',name,' at:',time.ctime()
        time.sleep(1)
        print 'end loop',name,' at:',time.ctime()
        
    class ThreadFunc(Thread):
        def __init__(self,func,args,name=''):
            super(ThreadFunc,self).__init__()
            self.name = name
            self.func = func
            self.args = args
    
        def run(self):
            self.func(*self.args)
    
    if __name__ == '__main__':
        loops = [2,4]
        nloops = range(len(loops))
        threads = []
        print 'starting at :',time.ctime()
        for i in nloops:
            t = 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 DONE at :',time.ctime()

     

    这种方法和第二种方法大同小异,主要就是在子类的时候集成Thread类,然后将可调用的方法修改为run方法即可,在Thread类对象中,如果需要继承,那么主要就是将run方法进行重写。

    执行结果如下:

     

    [root@python 522]# python thread_demo.py 
    starting at : Sun May 22 03:44:49 2016
    start loop 0  at: Sun May 22 03:44:49 2016
    start loop 1  at: Sun May 22 03:44:49 2016
    end loop 0  at: Sun May 22 03:44:50 2016
    end loop 1  at: Sun May 22 03:44:50 2016
    all DONE at : Sun May 22 03:44:50 2016

     

    在一般推荐的方法中,是使用第三种方法,也就是子类化Thread类。

    在这里用了三个循环,主要的目的是在进行创建的时候,不需要开启线程,从而在第二循环中,开启所有的线程。

     

    6、 Thread基本方法

    在threading的Thread类中,主要的方法是run方法,也就是在使用target初始化的时候关联的方法。

    Thread实例的时候,提供target参数,就是需要执行的函数,另外一个args参数就是函数的参数,必须用元祖的形式提供,如果只有一个参数,必须在一个参数后加一个逗号,表示为元祖。

    join表示主线程需不需要等待子线程,如果不需要,那么可以不调用join函数,在上面例子中,调用了join函数,从而主线程需要等待子线程,在使用了timeout参数之后,那么会表示主线程最多等多少秒,然后继续执行主线程,主线程结束,那么会将子线程结束掉

    setDaemon是设置线程为守护线程,守护线程表示一个不重要线程,主线程结束,那么守护线程也就结束了,可以使用方法isDaemon来判断是否为守护线程

    getname表示返回线程的名称,setname表示设置线程的名称,isalive表示线程是否还在运行

    在设置守护线程的时候,必须在启动方法也就是start之前进行设置。

     

    7、 线程之间的通信

    具体的流程如下:

    也就是生产者线程产生数据写入到共享数据中,然后消费者线程从共享数据中取出数据。

    具体代码如下:

     

    #!/usr/bin/env python
    
    from threading import Thread
    import time
    from Queue import Queue
    
    def writeQ(queue,i):
        while True:
            time.sleep(1)
            if queue.full():
                time.sleep(1)
            else:
                queue.put('xxx')
                print 'producting object for Q..',i
    
    def readQ(queue,i):
        while True:
            time.sleep(1)
            if queue.empty():
                time.sleep(1)
                print 'empty'
            else:
                val = queue.get()
                print 'consumed object from Q..size now:',queue.qsize(),' consumer ' ,i
    
    class Producter(Thread):
        def __init__(self,func,args,name=''):
            super(Producter,self).__init__()
            self.name = name
            self.args = args
            self.func = func
    
        def run(self):
            self.res = self.func(*self.args)
            #return self.res
    
    class Consumer(Thread):
        def __init__(self,func,args,name=''):
            super(Consumer,self).__init__()
            self.name = name
            self.args = args
            self.func = func
    
        def run(self):
            self.res = self.func(*self.args)
            #return self.res
    
    if __name__ =='__main__':
        q = Queue(100)
        for i in range(20):
            p = Producter(writeQ,(q,'production %s ' % i))
            p.start()
        for i in range(2):
            c = Consumer(readQ,(q,'consumer %s ' % i))
            c.start()

    在其中,主要就是使用Queue进行线程间的通信,在以上的代码中,可以进行优化,例如提取公共类,派生子类生产者和消费者。

     


    8、线程是不是可以无限创建

    一下测试为测试线程是不是可以无限创建,代码如下:

     

    [root@python 522]# cat thread_test.py 
    #!/usr/bin/env python
    
    from threading import Thread
    
    def testThread():
        while True:
            pass
    while True:
        t = Thread(target=testThread)
        t.start()


    就是无限的创建线程。

     

    vmstat基准线如下:

    指标解释如下:

    r 表示运行的队列,话说超过5,就很有压力了

    in 表示中断的次数

    cs 表示上下文切换

    us 表示用户cpu时间

    sy 表示系统的cpu时间

    运行一段时间后,发现,这个procs的r数量增长很快,那么表示,有多少个的线程分配到了cpu

    看出来,cpu很繁忙,中断的次数和上下文切换的数量很大,而且用户的cpu时间占比比较多。

    在这个时候,登录系统的时候,会发现系统非常卡,可以看出来脑子。。。CPU根本不够用了。

     

    杀掉进程之后恢复正常。。那么去掉cpu的限制,那么可以使用如下代码:

     

    #!/usr/bin/env python
    
    from threading import Thread
    from time import sleep
    
    i=0
    def testThread():
        print i 
    
    while True:
        i += 1
        t = Thread(target=testThread)
        t.start()


    这次测试。。好像没有线程的限制。

     

    公众号:SRE艺术

     


     

     

    展开全文
  • Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。 使用Condition的主要方式为:线程首先acquire一个...
  • Python中我们主要是通过thread和threading这两个模块来实现的,其中Python的threading模块是对thread做了一些包装的,可以更加方便的被使用,所以我们使用threading模块实现多线程编程。一般来说,使用线程有两种...
  • 主要介绍了Python多线程编程(四):使用Lock互斥锁,本文讲解了互斥锁概念、同步阻塞、代码示例等内容,需要的朋友可以参考下
  • 主要介绍了Python多线程编程简单介绍,本文讲解了创建线程、Thread对象函数、常用示例等内容,需要的朋友可以参考下
  • 我们想要了解Python多线程,就必须要了解GIL,GIL即全局解释锁。 举个栗子 计算机执行程序时,是需要把代码编译成机器指令再去执行的,我们现在用的编辑器,其实就是一种解释器,在我们右键运行程序时,它能够将整个文件...
  • python多线程编程.rar

    2021-06-10 13:08:54
    主要讲述Python多线程编程,主要讲述的thread库,threading库,Queue库等实现多线程编程
  • Python中我们主要是通过thread和 threading这两个模块来实现的,其中Python的threading模块是对thread做了一些包装的,可以更加方便的被使用,所以我们使用 threading模块实现多线程编程。这篇文章我们主要来看看...
  • 这个是在学习python多线程的时候自己总结的文档,对理解Python多线程非常有帮助,很多都是从官方文档而来的。
  • 使用threading.Event可以实现线程间相互通信,之前的Python:使用threading模块实现多线程编程七[使用Condition实现复杂同步]我们已经初步实现了线程间通信的基本功能,但是更为通用的一种做法是使用threading.Event...
  • 主要介绍了Python多线程编程(六):可重入锁RLock,本文直接给出使用实例,然后讲解如何使用RLock避免死锁,需要的朋友可以参考下
  • 在前面一篇博文《Python多线程编程(一):threading 模块 Thread 类的用法详解 》 我有简单介绍怎么利用 threading 模块进行多线程的编码。 但那只是多线程编码最简单的部分,真正难的其实是多个线程之间的通信和...

    在前面一篇博文《Python多线程编程(一):threading 模块 Thread 类的用法详解 》 我有简单介绍怎么利用 threading 模块进行多线程的编码。

    但那只是多线程编码最简单的部分,真正难的其实是多个线程之间的通信和数据同步。

    大概可以这样讲,多线程最难的是如何正确协调各个线程修改同一份数据。

    以卖票的例子作为说明。

    买票有多个窗口,假设有 3 个好了,窗口之间共享一个票池,每个窗口都可以买票直到票池里面没有票可以卖。

    不难写出下面的代码。

    import threading
    import random
    
    class WindowThread(threading.Thread):
    
        def __init__(self,name):
            threading.Thread.__init__(self,name=name)
            self.name = name
            self.tickts = 0
    
        def run(self):
            global tickt_count
    
            while tickt_count > 0:
    
                print('%s notice:There has %d tickts remain ' %(self.name,tickt_count))
    
                if tickt_count > 2:
                    number = random.randint(1,2)
                else:
                    number = 1
    
                tickt_count -= number
                self.tickts += number
    
                print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
                      % (self.name, number, tickt_count, self.tickts))
    
            print('%s notice:There is no tickt can sold! Already sold %d'%(self.name,self.tickts))
    
    
    tickt_count = 10
    
    window1 = WindowThread('window1')
    window2 = WindowThread('window2')
    window3 = WindowThread('window3')
    
    window1.start()
    window2.start()
    window3.start()
    window1.join()
    window2.join()
    window3.join()
    
    print('tickt count ',tickt_count)
    

    假设有 3 个窗口,总共有 10 张票。

    在每个窗口排队购票时,窗口会告诉你当前还剩多少票。

    if tickt_count > 2:
          number = random.randint(1,2)
       else:
          number = 1
    
    tickt_count -= number
    self.tickts += number
    

    每个人最多购买 2 张票,如果票池当中只剩下 1 张票时,那么也只能购买一张票了。

    tickt_count 是票池里面剩余的票的数量。
    tickts 是每个窗口已经卖出的数量。

    购买结束后,要更新票池里面剩余票的数量,和更新本窗口卖出去的票数。

    最后,所有的窗口无票可卖的时候,结束。然后打印 票池里面的票数,理论上应该是 0,因为票已经卖光了。

    我们看结果运行如何:

    window1 notice:There has 10 tickts remain 
    window2 notice:There has 10 tickts remain 
    window1 have buy 2 tickt,the remain tickt't count is 8 .Already buy 2 
    window2 have buy 1 tickt,the remain tickt't count is 7 .Already buy 1 
    
    window1 notice:There has 7 tickts remain 
    window1 have buy 2 tickt,the remain tickt't count is 5 .Already buy 4 
    
    window1 notice:There has 5 tickts remain 
    window1 have buy 1 tickt,the remain tickt't count is 4 .Already buy 5 
    
    window1 notice:There has 4 tickts remain 
    window1 have buy 1 tickt,the remain tickt't count is 3 .Already buy 6 
    
    window1 notice:There has 3 tickts remain 
    window1 have buy 2 tickt,the remain tickt't count is 1 .Already buy 8 
    
    window3 notice:There has 1 tickts remain 
    window1 notice:There has 1 tickts remain 
    window1 have buy 1 tickt,the remain tickt't count is 0 .Already buy 9 
    
    window1 notice:There is no tickt can sold! Already sold 9
    window3 have buy 1 tickt,the remain tickt't count is -1 .Already buy 1 
    
    window3 notice:There is no tickt can sold! Already sold 1
    
    window2 notice:There is no tickt can sold! Already sold 1
    tickt count  -1
    

    多运行几次,可以发现一个现象,那就是结果不正确。

    3 个窗口的卖出的总数对不上。

    最后显示 tickt count 为 -1,而不是 0,这显然不符合我们的预期。

    Q:为什么会这样呢?

    A:因为我们没有保护好多个线程之间,共享的数据。

    Q:怎么能解决呢?

    A:Lock 机制可以解决

    什么是 Lock?

    Lock 中文称为锁,是一种初级的多线程同步的手段。

    Lock 有 locked 和 unlocked 两种状态,而这两中状态之间是可以转换的.

    • 当 Lock 是 unlocked 状态时候,某个线程调用 acquire() 可以获取这个 Lock,并且将 Lock将状态转换成 locked 状态,并且线程不会阻塞。
    • 但当 Lock 是 locked 状态时,某个线程调用 acquire() 会阻塞自己,直到其他的线程将 Lock 的状态变成 unlocked。
    • 当 Lock 是 locked 状态时,调用 release() 方法,可以释放一个 Lock,这样其它线程就可以获取这个 Lock 了。
    • 但当 Lock 是 unlocked 状态时,某个线程调用 release(),程序会抛出 RuntimeError 异常。

    所以,acquire() 和 release() 方法在单个线程当中都是成对使用的。

    在这里插入图片描述

    有效利用 Lock 的状态转换机制,就可以避免多个线程同时修改同一份数据。

    于是,我们可以进行代码的改写。

    import threading
    import random
    
    
    class WindowThread(threading.Thread):
    
        def __init__(self,name,lock):
            threading.Thread.__init__(self,name=name)
            self.name = name
            self.tickts = 0
            self.lock = lock
    
        def run(self):
            global tickt_count
    
            while tickt_count > 0:
    
                print('%s notice:There has %d tickts remain ' %(self.name,tickt_count))
    
                self.lock.acquire()
                if tickt_count > 0:
                    if tickt_count > 2:
                        number = random.randint(1,2)
                    else:
                        number = 1
                    tickt_count -= number
                    self.tickts += number
    
                    print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
                          % (self.name, number, tickt_count, self.tickts))
                self.lock.release()
    
    
            print('%s notice:There is no tickt can sold! Already sold %d'%(self.name,self.tickts))
    
    
    tickt_count = 10
    
    lock = threading.Lock()
    
    window1 = WindowThread('window1',lock)
    window2 = WindowThread('window2',lock)
    window3 = WindowThread('window3',lock)
    
    window1.start()
    window2.start()
    window3.start()
    window1.join()
    window2.join()
    window3.join()
    
    print('tickt count ',tickt_count)
    

    还是 3 个线程,但代码少许不一样。

    lock = threading.Lock()
    
    window1 = WindowThread('window1',lock)
    window2 = WindowThread('window2',lock)
    window3 = WindowThread('window3',lock)
    

    3 个线程共用 1 个 Lock 对象。

    self.lock.acquire()
    if tickt_count > 0:
       if tickt_count > 2:
           number = random.randint(1,2)
       else:
           number = 1
       tickt_count -= number
       self.tickts += number
    
       print('%s have buy %d tickt,the remain tickt\'t count is %d .Already buy %d \n'
             % (self.name, number, tickt_count, self.tickts))
    self.lock.release()
    

    进行关键数据操作的时候,用 Lock 锁起来,这样每次就只能一个线程对 tickt_count 数量进行修改。

    最终程序结果运行如下:

    window1 notice:There has 10 tickts remain 
    window1 have buy 1 tickt,the remain tickt't count is 9 .Already buy 1 
    
    window2 notice:There has 9 tickts remain 
    window1 notice:There has 9 tickts remain 
    window2 have buy 1 tickt,the remain tickt't count is 8 .Already buy 1 
    
    window3 notice:There has 8 tickts remain 
    window2 notice:There has 8 tickts remain 
    window2 have buy 2 tickt,the remain tickt't count is 6 .Already buy 3 
    
    window2 notice:There has 6 tickts remain 
    window3 have buy 2 tickt,the remain tickt't count is 4 .Already buy 2 
    
    window3 notice:There has 4 tickts remain 
    window2 have buy 2 tickt,the remain tickt't count is 2 .Already buy 5 
    
    window2 notice:There has 2 tickts remain 
    window1 have buy 1 tickt,the remain tickt't count is 1 .Already buy 2 
    
    window1 notice:There has 1 tickts remain 
    window3 have buy 1 tickt,the remain tickt't count is 0 .Already buy 3 
    
    window3 notice:There is no tickt can sold! Already sold 3
    window2 notice:There is no tickt can sold! Already sold 5
    window1 notice:There is no tickt can sold! Already sold 2
    tickt count  0
    

    可以多试几次,窗口卖出的票数都是可以对的上号的,并且最终票池里面的数量是 0,不会发生之前出现为 -1 的情况。

    所以,自此我们就通过引入 Lock 同步机制,进行了一个很简单化的多线程编码示例。

    默认情况,当一个 Lock 是 locked 状态时调用 acquire(),会阻塞线程本身。

    但我们可以设置不阻塞,或者是阻塞指定时间。

    #不阻塞
    lock.acquire(False)
    
    #阻塞指定时间,如 3 秒钟,当然 python3 的版本才有这个功能
    lock.acquire(timeout=3)
    
    

    我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=335haneiijggk

    展开全文
  • 我们进行程序开发的时候,肯定避免不了要处理并发的情况。...本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。 所以,我们要在代码中引用它。 import threading thread...

    我们进行程序开发的时候,肯定避免不了要处理并发的情况。

    一般并发的手段有采用多进程和多线程。

    但线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。

    Python 提供多线程编程的方式。

    本文基于 Python3 讲解,Python 实现多线程编程需要借助于 threading 模块。

    所以,我们要在代码中引用它。

    import threading
    

    threading 模块中最核心的内容是 Thread 这个类。

    我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。

    值得注意的是,程序运行时默认就是在主线程上

    创建 Thread 对象有 2 种手段。

    1. 直接创建 Thread ,将一个 callable 对象从类的构造器传递进去,这个 callable 就是回调函数,用来处理任务。
    2. 编写一个自定义类继承 Thread,然后复写 run() 方法,在 run() 方法中编写任务处理代码,然后创建这个 Thread 的子类。

    1. 直接创建 Thread 对象。

    class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
    

    Thread 的构造方法中,最重要的参数是 target,所以我们需要将一个 callable 对象赋值给它,线程才能正常运行。

    如果要让一个 Thread 对象启动,调用它的 start() 方法就好了。

    下面是代码示例。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print('test ',i)
            time.sleep(1)
    
    
    thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print('main ', i)
        time.sleep(1)
    

    上面代码很简单,在主线程上打印 5 次,在一个子线程上打印 5 次。

    运行结果如下:

    test  0
    main  0
    main  1
    test  1
    main  2
    test  2
    main  3
    test  3
    main  4
    test  4
    

    上面的 callable 没有参数,如果需要传递参数的话,args 是固定参数,kwargs 是可变参数。

    Thread 的名字

    每一个 Thread 都有一个 name 的属性,代表的就是线程的名字,这个可以在构造方法中赋值。

    如果在构造方法中没有个 name 赋值的话,默认就是 “Thread-N” 的形式,N 是数字。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(1)
    
    
    thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        time.sleep(1)
    

    通过 thread.current_thread() 方法可以返回线程本身,然后就可以访问它的 name 属性。

    上面代码运行结果如下:

    Thread-1 test  0
    MainThread main  0
    Thread-1 test  1
    MainThread main  1
    Thread-1 test  2
    MainThread main  2
    Thread-1 test  3
    MainThread main  3
    Thread-1 test  4
    MainThread main  4
    

    如果我们在 Thread 对象创建时,构造方法里面赋值。

    thread = threading.Thread(target=test,name='TestThread')
    

    那么,运行结果会变成这个样子。

    TestThread test  0
    MainThread main  0
    MainThread main  1
    TestThread test  1
    MainThread main  2
    TestThread test  2
    MainThread main  3
    TestThread test  3
    MainThread main  4
    TestThread test  4
    

    Thread 的生命周期

    1. 创建对象时,代表 Thread 内部被初始化。
    2. 调用 start() 方法后,thread 会开始运行。
    3. thread 代码正常运行结束或者是遇到异常,线程会终止。

    可以通过 Thread 的 is_alive() 方法查询线程是否还在运行。

    值得注意的是,is_alive() 返回 True 的情况是 Thread 对象被正常初始化,start() 方法被调用,然后线程的代码还在正常运行。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(0.5)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    # thread = threading.Thread(target=test)
    thread.start()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    在上面的代码中,我让 TestThread 比 MainThread 早一点结束,代码运行结果如下。

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    TestThread test  1
    MainThread main  1
    TestThread is alive  True
    TestThread test  2
    TestThread test  3
    MainThread main  2
    TestThread is alive  True
    TestThread test  4
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    我们可以看到,主线程通过调用 TestThread 的 isAlive() 方法,准确查询到了它的存货状态。

    join() 提供线程阻塞手段。

    上面代码两个线程是同时运行的,但如果让一个先运行,一个后运行,怎么做呢?

    调用一个 Thread 的 join() 方法,可以阻塞自身所在的线程。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(0.5)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    thread.start()
    thread.join()
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    主线程创建了 TestThread 对象后,让其 start,然后通过调用 join() 方法,实现等待。程序运行结果如下:

    TestThread test  0
    TestThread test  1
    TestThread test  2
    TestThread test  3
    TestThread test  4
    MainThread main  0
    TestThread is alive  False
    MainThread main  1
    TestThread is alive  False
    MainThread main  2
    TestThread is alive  False
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    默认的情况是,join() 会一直等待对应线程的结束,但可以通过参数赋值,等待规定的时间就好了。

    def join(self, timeout=None):
    

    timeout 是一个浮点参数,单位是秒。

    如果我们更改上面的代码。

    thread.join(1.0)
    

    它的结果会是这样。

    TestThread test  0
    TestThread test  1
    MainThread main  0
    TestThread is alive  True
    TestThread test  2
    TestThread test  3
    MainThread main  1
    TestThread is alive  True
    TestThread test  4
    MainThread main  2
    TestThread is alive  False
    MainThread main  3
    TestThread is alive  False
    MainThread main  4
    TestThread is alive  False
    

    主线程只等待了 1 秒钟。

    Thread 中的 daemon 属性

    有同学可能会注意到,Thread 的构造方法中有一个 daemon 参数。默认是 None。

    那么,daemon 起什么作用呢?

    我们先看一段示例代码。

    import threading
    import time
    
    def test():
    
        for i in range(5):
            print(threading.current_thread().name+' test ',i)
            time.sleep(2)
    
    
    thread = threading.Thread(target=test,name='TestThread')
    # thread = threading.Thread(target=test,name='TestThread',daemon=True)
    thread.start()
    
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    我们让主线程执行代码的时长比 TestThread 要短。

    程序运行结果如下。

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    MainThread main  1
    TestThread is alive  True
    TestThread test  1
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  2
    MainThread main  4
    TestThread is alive  True
    TestThread test  3
    TestThread test  4
    

    MainThread 没有代码运行的时候,TestThread 还在运行。

    这是因为 MainThread 在等待其他线程的结束。

    TestThread 中 daemon 属性默认是 False,这使得 MainThread 需要等待它的结束,自身才结束。

    如果要达到,MainThread 结束,子线程也立马结束,怎么做呢?

    其实很简单,只需要在子线程调用 start() 方法之前设置 daemon 就好了。

    当然也可以在子线程的构造器中传递 daemon 的值为 True。

    thread = threading.Thread(target=test,name='TestThread',daemon=True)
    # thread.setDaemon(True)
    

    更改前面代码示例,运行结果如下

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    MainThread main  1
    TestThread is alive  True
    TestThread test  1
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  2
    MainThread main  4
    TestThread is alive  True
    

    可以看到 MainThread 结束了 TestThread 也结束了。

    2.自定义类继承 Thread

    前面讲过,直接初始化一个 Thread,然后,现在还有一种方式就是自定义一个 Thread 的子类,然后复写它的 run() 方法。

    import threading
    import time
    
    
    class TestThread(threading.Thread):
    
        def __init__(self,name=None):
            threading.Thread.__init__(self,name=name)
    
        def run(self):
            for i in range(5):
                print(threading.current_thread().name + ' test ', i)
                time.sleep(1)
    
    
    thread = TestThread(name='TestThread')
    thread.start()
    
    
    for i in range(5):
        print(threading.current_thread().name+' main ', i)
        print(thread.name+' is alive ', thread.isAlive())
        time.sleep(1)
    

    上面的代码,我们自定义了 TestThread 这个类,然后继承了 threading.Thread。

    只有在 run() 方法中处理逻辑。最终代码运行结果如下:

    TestThread test  0
    MainThread main  0
    TestThread is alive  True
    TestThread test  1
    MainThread main  1
    TestThread is alive  True
    TestThread test  2
    MainThread main  2
    TestThread is alive  True
    MainThread main  3
    TestThread is alive  True
    TestThread test  3
    MainThread main  4
    TestThread test  4
    TestThread is alive  True
    

    这与之前的效果并无差异,但我还是推荐用这种方法,毕竟面向对象编程嘛。

    自此,Python 多线程编码技术就大致介绍完毕,大家可以进行实际代码编写了。

    但是,多线程编程的难点在于多个线程之间共享数据的同步,这是非常容易出错的地方,我将分别编写相应的博文去介绍一些高级的技术点。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 99,817
精华内容 39,926
关键字:

python多线程编程

python 订阅