zoukankan      html  css  js  c++  java
  • 重谈异步编程-多线程

    Python中的多线程需要导入threading包。

    线程在操作系统中有过介绍,就不再过多阐述了。直接通过代码演示。

    如何创建线程?

    import threading
    def fun():
        pass
    t1 = threading.Thread(target=fun)
    print(t1)

    输出:

    <Thread(Thread-1, initial)>

    可以看到,线程的创建不需要像协程一样写协程函数。fun就是一个普通的函数。

    输出的t1是Thread对象,也就是线程对象。后边的Thread-1是协程的名字。initial是协程的状态,因为协程还没开始运行,所以状态是初态。

    创建线程是用的threading.Thread()方法创建的,Thread方法中包含的参数有很多。做一下简单介绍。

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

    group用的比较少,不说;target传入的是将哪个函数加入到该线程里边,所以这个位置参数是一个函数名称;name是给线程起名字,一般是用字符串;args是刚才传入函数的位置参数的实参,注意要用元组的形式;kwargs是刚才传入函数的关键字参数的实参;daemon是把该线程设定为是否是守护线程,写True或False,是一个布尔值,后边我们会详细讲到。

    下边我们将上边的代码丰富一下。

    import threading
    def fun(age,**kwargs):
        print('我叫',kwargs['name'],',今年',age,'岁了',sep='')
    t1 = threading.Thread(target=fun,args=(18,),name = 't1',kwargs={'name':'张三'})
    t1.start()
    print(t1)

    输出:

    我叫张三,今年18岁了
    <Thread(t1, stopped 14028)>

    可以看到,线程的名字已经成功改为了t1,线程的状态已经是停止了。

    那么我们学线程的目的是什么?为了实现线程的并发操作。

    例:

    import threading
    import time
    def fun():
        print('hello')
        time.sleep(2)
        print('world')
    def fun1():
        print('how are you?')
        time.sleep(2)
        print('fine')
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()

    输出:

    hello
    how are you?
    world
    fine

    好的,看起来结果确实是我们需要的,交替进行,通过观察输出的话,能够看到整个程序执行是需要2s的时间。

    我们用程序来计算一下时间:

    例:

    import threading
    import time
    def fun():
        print('hello')
        time.sleep(2)
        print('world')
    def fun1():
        print('how are you?')
        time.sleep(2)
        print('fine')
    start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()
    end_time = time.time()
    print(end_time-start_time)

    输出:

    hello
    how are you?
    0.001026153564453125
    world
    fine

    哎呀,我们发现输出并不是我们想要的,首先是输出时间的位置不对,输出的时间也不对,很明显,输出时间的代码在开始就被执行了,那这是为什么呢?

    我们加一行代码,打印一下:threading.enumerate(),这个enumerate方法的功能是返回线程的信息,即有多少个线程,和线程的名字,状态。

    import threading
    import time
    def fun():
        print('hello')
        time.sleep(2)
        print('world')
    def fun1():
        print('how are you?')
        time.sleep(2)
        print('fine')
    #start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()
    # end_time = time.time()
    # print(end_time-start_time)
    print(threading.enumerate())

    输出:

    hello
    how are you?
    [<_MainThread(MainThread, started 2404)>, <Thread(t1, started 4660)>, <Thread(t2, started 2952)>]
    fine
    world

    我们发现,输出的内容中间,线程居然是3个,除了t1和t2之外,还有一个主线程。在上述代码中,主线程、t1、t2是同时执行的,也就是说并不是我们想的那样,t1和t2交叉执行完之后,再执行时间的计算输出,那我们如何解决这问题呢?也就是说等t1和t2执行完毕之后,再执行主线程?加两行代码就可以,jion()。

    例:

    import threading
    import time
    def fun():
        print('hello')
        time.sleep(2)
        print('world')
    def fun1():
        print('how are you?')
        time.sleep(2)
        print('fine')
    start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print(end_time-start_time)

    输出:

    hello
    how are you?
    fine
    world
    2.0018341541290283

    发现,没有问题了。那么这里Thread.jion()的作用是什么呢?简单来说,就是等子线程结束之后,再执行主线程。准确一点是说,主线程碰到了Thread.jion(),会阻塞,一直等到子进程执行完,再唤醒主线程,主线程继续执行。另外Thread.jion()中还有一个参数,就是timeout,简单来说,就是主线程最多阻塞这么多时间,如果子线程还没有结束,就不等了,继续执行主线程。

    
    

    输出:

    hello
    how are you?
    0.716256856918335
    worldfine

    ====================================================================================================================================================================================================

    那么下边一个话题,多线程并发真的能够节省时间吗?

    我们看两个例子,一个是CPU密集型的两个线程,一个是IO为主的两个线程。

    CPU密集型:

    import threading
    import time
    def fun():
        for i in range(10000000):
            sum = i*i*i*i
    def fun1():
        for i in range(10000000):
            sum = i*i*i
    start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print('time',end_time-start_time)

    输出:

    time 2.053481340408325
    import time
    def fun():
        for i in range(10000000):
            sum = i*i*i*i
    def fun1():
        for i in range(10000000):
            sum = i*i*i
    start_time = time.time()
    fun()
    fun1()
    end_time = time.time()
    print('time',end_time-start_time)

    输出:

    time 2.016674518585205

    我们可以看到,使用多线程和不适用多线程,耗时差不多,并且多线程甚至还略微多一丢丢时间,是因为线程的切换也是有时间开销的。

    IO型为主:

    import threading
    import time
    def fun():
        time.sleep(1)
    def fun1():
        time.sleep(1)
    start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1')
    t2 = threading.Thread(target=fun1,name = 't2')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print('time',end_time-start_time)

    输出:

    time 1.0026021003723145
    import time
    def fun():
        time.sleep(1)
    def fun1():
        time.sleep(1)
    start_time = time.time()
    fun()
    fun1()
    end_time = time.time()
    print('time',end_time-start_time)

    输出:

    time 2.0009641647338867

    我们可以看到,IO型任务的情况下,多线程确实是要快大约一倍。在例子中,我们用time.sleep()来模拟IO。在真正的IO中,CPU是不用处理IO工作的,在time.sleep()中,CPU也是不需要工作的,所以是一样的。

    那么我有一个结论:如果线程任务是以CPU密集型为主,计算时间是不会缩减,反而会略微增加;如果线程任务是以IO为主的,计算时间会较少很多,甚至一半。这是为什么呢?这是因为Python中有GIL锁,简单来说,即使计算机有多个CPU,也只能有一个线程工作。那么为什么多线程在爬虫中也有应用,可以一定程度的提高效率呢?是因为爬虫本身就是下载数据,也就是IO工作。

    ====================================================================================================================================================================================================

    我们来讲下一个话题,在最开始介绍Theard方法时,提到了守护线程,那么什么是守护线程呢?我们仍然以上边例子为例,进行修改。

    import threading
    import time
    def fun():
        print('hello')
        time.sleep(3)
        print('world')
    def fun1():
        print('how are you?')
        time.sleep(3)
        print('fine')
    start_time = time.time()
    t1 = threading.Thread(target=fun,name = 't1',daemon=True)
    t2 = threading.Thread(target=fun1,name = 't2',daemon=True)
    t1.start()
    t2.start()
    t1.join(timeout=0.3)
    t2.join(timeout=0.4)
    end_time = time.time()
    print(end_time-start_time)

    输出:

    hello
    how are you?
    0.7178466320037842

    可以看到,t1和t2的最后一个输出并没有执行,程序就结束了。在t1和t2的实例化中,只是加入了参数的设置:daemon=True。这就是守护线程。

    总结说,如果daemon=False(默认值),该进程为非守护进程,反之如果daemon=True,该进程就是守护进程。守护,顾名思义,守护并且共存亡的意思。主线程结束,子线程也结束。

    ====================================================================================================================================================================================================

    再谈下一个话题,如何保证两个子进程交替执行。

    例:

    import threading
    import time
    # 定义线程运行函数
    def ou():
        for i in range(0,10,2):
            print(i)
            time.sleep(0.5)
    def ji():for i in range(1,10,2):
            print(i)
            time.sleep(0.5)
    if __name__ == '__main__':
        th = threading.Thread(target=ji)
        th2 = threading.Thread(target=ou)
        th.start()
        th2.start()

    输出:

    0
    1
    23
    
    5
    4
    76
    
    89

    我们发现输出并没有规律,但是我就是想让两个进程交替执行,输出的奇偶交叉,怎么办?

    可以参考我前边的案例。

  • 相关阅读:
    关于AysncController的一次测试(url重写后静态页文件内容的读取是否需要使用异步?)
    JQuery笔记
    ABP文档笔记
    ABP文档笔记
    ABP文档笔记
    设计模式、架构设计 博文收集
    async/await 的一些知识 (死锁问题)
    ABP文档笔记
    ABP文档笔记系列
    ABP文档笔记
  • 原文地址:https://www.cnblogs.com/lgwdx/p/15509621.html
Copyright © 2011-2022 走看看