• python多线程和多进程


    python多线程

     

    实例一:最简单的多线程

     

    python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例。

    创建线程 python中有两种方式实现线程:

    实例化一个threading.Thread的对象,并传入一个初始化函数对象(initial function )作为线程执行的入口; 继承threading.Thread,并重写run函数;

    In [2]:
    import threading, time
    
    
    def run(n):
        print("task  ", n)
        time.sleep(2)
    
    
    start_time = time.time()
    t1 = threading.Thread(target=run, args=("t1",))
    t2 = threading.Thread(target=run, args=("t2",))
    
    t1.start()
    t2.start()
    # t1.join()
    # t2.join()
    print(time.time() - start_time)
    
     
    task   t1
    task  0.004959583282470703 t2
    
    
     

    不加join的话,主线程和子线程完全是并行的,加了join主线程得等这个子线程执行完毕,才能继续往下走。这样才能得到这个程序真正的运行时间。

    In [4]:
    start_time = time.time()
    t1 = threading.Thread(target=run, args=("t1",))
    t2 = threading.Thread(target=run, args=("t2",))
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(time.time() - start_time)
    
     
    task   t1
    task   t2
    2.018137216567993
    
     

    实例二:继承式调用多线程

     

    创建一个新的类来支持多线程,继承了threading.Thread类

    In [5]:
    import threading, time
    
    
    class MyThread(threading.Thread):
        def __init__(self, n, sleep_time):
            super(MyThread, self).__init__()
            self.n = n
            self.sleep_time = sleep_time
            
        def run(self):
            print("run task", self.n)
            time.sleep(2)
            print("task done,", self.n)
    
    
    t1 = MyThread("t1", 2)
    t2 = MyThread("t2", 4)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('main')
    
     
    run task t1
    run task t2
    task done,task done, t2
     t1
    main
    
     

    threading模块的一些属性和方法可以参照官网,这里重点介绍一下threading.Thread对象的方法

    下面是threading.Thread提供的线程对象方法和属性:

    start():创建线程后通过start启动线程,等待CPU调度,为run函数执行做准备;
    run():线程开始执行的入口函数,函数体中会调用用户编写的target函数,或者执行被重载的run函数;
    join([timeout]):阻塞挂起调用该函数的线程,直到被调用线程执行完成或超时。通常会在主线程中调用该方法,等待其他线程执行完成。
    name、getName()&setName():线程名称相关的操作;
    ident:整数类型的线程标识符,线程开始执行前(调用start之前)为None;
    isAlive()、is_alive():start函数执行之后到run函数执行完之前都为True;
    daemon、isDaemon()&setDaemon():守护线程相关;
    这些是我们创建线程之后通过线程对象对线程进行管理和获取线程信息的方法。

     

    实例三: 一次性启动多个线程

     

    第一个程序,使用循环来创建线程,但是这个程序中一共有51个线程,我们创建了50个线程,但是还有一个程序本身的线程,是主线程。这51个线程是并行的。注意:这个程序中是主线程启动了子线程。

    In [6]:
    import threading, time
    
    
    def run(n):
        print("task ", n)
        time.sleep(2)
        print("task done,", n)
    
    
    start_time = time.time()
    for i in range(50):
        t = threading.Thread(target=run, args=("t- %s" % i,))
        t.start()
    
    print("cost  :", time.time() - start_time)
    
     
    task  t- 0
    task  t- 1
    task  t- 2
    task  t- 3
    task  t- 4
    task  t- 5
    task  t- 6
    task  t- 7
    task  t- 8
    task  t- 9
    task  t- 10
    task  t- 11
    task  t- 12
    task  t- 13
    task  t- 14
    task  t- 15
    task  t- 16
    task  t- 17
    task  t- 18
    task  t- 19
    task  t- 20
    task  t- 21
    task  t- 22
    task  t- 23
    task  t- 24
    task  t- 25
    task  t- 26
    task  t- 27
    task  t- 28
    task  t- 29
    task  t- 30
    task  t- 31
    task  t- 32
    task  t- 33
    task  t- 34
    task  t- 35
    task  t- 36
    task  t- 37
    task  t- 38
    task  t- 39
    task  t- 40
    task  t- 41
    task  t- 42
    task  t- 43
    task  t- 44
    task  t- 45
    task  t- 46
    task  t- 47
    task  t- 48
    task  t- 49
    cost  : 0.09465932846069336
    
     

    我们观察结果会发现,程序显示的执行时间只有0.007秒,这是因为最后一个print函数它存在于主线程,而整个程序主线程和所有子线程是并行的,那么可想而知,在子线程还没有执行完毕的时候print函数就已经执行了,总的来说,这个时间只是执行了一个线程也就是主线程所用的时间。

     

    接下来这个程序,吸取了上面这个程序的缺点,创建了一个列表,把所有的线程实例都存进去,然后使用一个for循环依次对线程实例调用join方法,这样就可以使得主线程等待所创建的所有子线程执行完毕才能往下走。注意实验结果:和两个线程的结果都是两秒多一点

    In [7]:
    import threading, time
    
    
    def run(n):
        print("task ", n)
        time.sleep(2)
        print("task done,", n)
    
    
    start_time = time.time()
    t_obj = []
    for i in range(50):
        t = threading.Thread(target=run, args=("t- %s" % i,))
        t_obj.append(t)
        t.start()
    
    for t in t_obj:
        t.join()
    
    print("cost  :", time.time() - start_time)
    
     
    task  t- 0
    task  t- 1
    task  t- 2
    task  t- 3
    task  t- 4
    task  t- 5
    task  t- 6
    task  t- 7
    task  t- 8
    task  t- 9
    task  t- 10
    task  t- 11
    task  t- 12
    task  t- 13
    task  t- 14
    task  t- 15
    task  t- 16
    task  t- 17
    task  t- 18
    task  t- 19
    task  t- 20
    task  t- 21
    task  t- 22
    task  t- 23
    task  t- 24
    task  t- 25
    task  t- 26
    task  t- 27
    task  t- 28
    task  t- 29
    task  t- 30
    task  t- 31
    task  t- 32
    task  t- 33
    task  t- 34
    task  t- 35
    task  t- 36
    task  t- 37
    task  t- 38
    task  t- 39
    task  t- 40
    task  t- 41
    task  t- 42
    task  t- 43
    task  t- 44
    task  t- 45
    task  t- 46
    task  t- 47
    task  t- 48
    task  t- 49
    task done,task done, t- 1
     t- 0
    task done,task done, task done,task done, t- 4
    task done, t- 3 t- 2
    
    task done,t- 5
     t- 7
     t- 6
    task done, t- 8
    task done,task done, t- 15
    task done, t- 12
    task done, t- 14
    task done, t- 9
    task done,task done, t- 11
     t- 10
     t- 13
    task done,task done,task done,task done,task done, t- 23
     t- 18
    task done, t- 19
    task done, t- 17
     t- 16
     t- 22
    task done, t- 21
     t- 20
    task done,task done, t- 27 t- 31
    task done, t- 24task done, t- 29
    
    
    task done,task done, t- 28
     t- 30
    task done,task done, t- 26
     t- 25
    task done,task done, task done, t- 34
     t- 32
    t- 36
    task done,task done, t- 37
     t- 35
    task done, t- 33
    task done,task done, t- 42
     t- 44
    task done,task done, t- 45task done, t- 40
    task done, t- 39
    
    task done,task done, t- 38
     t- 43
     t- 41
    task done,task done, t- 49task done,task done, t- 46
     t- 47
    
     t- 48
    cost  : 2.1159629821777344
    
    In [8]:
    import threading
    import time
    
    
    def run(n):
        print("task", n)
        time.sleep(2)
        print("task has done!")
        
        
    start_time = time.time()
    for i in range(50):
        t = threading.Thread(target=run, args=("t-%s" % i,))
        t.setDaemon(True)  # 把当前线程设置为守护线程,一定在start前设置
        t.start()
    print(threading.current_thread(), threading.active_count())
    print(time.time() - start_time)
    
     
    task task t-1
    t-0
    task t-2
    task t-3
    task t-4
    task t-5
    task t-6
    task t-7
    task t-8
    task t-9
    task t-10
    task t-11
    tasktask t-13
     t-12
    task t-14
    task t-15
    task t-16
    task t-17
    task t-18
    task t-19
    task t-20
    task t-21
    task t-22
    task t-23
    task t-24
    task t-25
    task t-26
    task t-27
    task t-28
    task t-29
    task t-30
    task t-31
    task t-32
    task t-33
    task t-34
    task t-35
    task t-36
    task t-37
    task t-38
    task t-39
    task t-40
    task t-41
    task t-42
    task t-43
    task t-44
    task t-45
    task t-46
    task t-47
    task t-48
    task t-49
    <_MainThread(MainThread, started 14164)> 55
    0.09246182441711426
    
     

    注意观察实验结果,并没有执行打印task has done,并且程序执行时间极其短。 这是因为在主线程启动子线程前把子线程设置为守护线程。 只要主线程执行完毕,不管子线程是否执行完毕,就结束。但是会等待非守护线程执行完毕 主线程退出,守护线程全部强制退出。皇帝死了,仆人也跟着殉葬

     

    实例四:线程锁(互斥锁Mutex)

     

    介绍一下python的GIL,全局解释器锁。并不是python的特性,是实现python解析器的时候引入的概念。这个锁是为了保证同一份数据不能被多个线程同时修改。因为cpython是使用c封装的,所以线程也是用c语言实现的,在和cpu交互的时候使用的c接口。(java,c + +等语言的自己实现的线程,所以自己可以直接控制cpu),而python就创造了一个全局解释器锁,来保证同一份数据不能被多个线程同时修改。 所以,这就造成了python的一个缺陷,无论多少核的机器,同一时刻只能有一个线程在执行。jpython没有这个问题。

    In [9]:
    import time
    import threading
    
    
    def run():
        lock.acquire()  #修改数据前加锁
        global num
        num += 1
        lock.release()  # 修改完后释放
    
    
    lock = threading.Lock()
    num = 0
    t_objs = []
    for i in range(1000):
        t = threading.Thread(target=run)
        t.start()
        t_objs.append(t)
    for t in t_objs:  # 循环线程实例列表,等待子线程执行完毕
        t.join()
    print(num)
    
     
    1000
    
    In [17]:
    import time
    import threading
    
    
    def run():
        # lock.acquire()  #修改数据前加锁
        global num
        num += 1
        # lock.release()  # 修改完后释放
    
    
    # lock = threading.Lock()
    num = 0
    t_objs = []
    for i in range(1000):
        t = threading.Thread(target=run)
        t.start()
        t_objs.append(t)
    for t in t_objs:  #循环线程实例列表,等待子线程执行完毕
        t.join()
    print(num)
    
     
    1000
    
     

    注意:gil只是为了减低程序开发复杂度。但是在2.几的版本上,需要加用户态的锁(gil的缺陷)而在3点几的版本上,加锁不加锁都一样。

     

    python多进程

     

    相比较于threading模块用于创建python多线程,python提供multiprocessing用于创建多进程。先看一下创建进程的两种方式。 创建进程

    创建进程的方式和创建线程的方式类似:

    实例化一个multiprocessing.Process的对象,并传入一个初始化函数对象(initial function )作为新建进程执行入口;
    继承multiprocessing.Process,并重写run函数;

    In [11]:
    # 方式1:
    from multiprocessing import Process
    import os, time
    
    
    def pstart(name):
        # time.sleep(0.1)
        print("Process name: %s, pid: %s " % (name, os.getpid()))
    
    
    if __name__ == "__main__":
        subproc = Process(target=pstart, args=('subprocess',))
        subproc.start()
        subproc.join()
        print("subprocess pid: %s" % subproc.pid)
        print("current process pid: %s" % os.getpid())
    
     
    subprocess pid: 8800
    current process pid: 15120
    
    In [18]:
    from multiprocessing import Process
    import os, time
    
    
    class CustomProcess(Process):
        def __init__(self, p_name, target=None):
            # step 1: call base __init__ function()
            super(CustomProcess, self).__init__(name=p_name, target=target, args=(p_name,))
    
        def run(self):
            # step 2:
            # time.sleep(0.1)
            print("Custom Process name: %s, pid: %s " % (self.name, os.getpid()))
    
    
    if __name__ == '__main__':
        p1 = CustomProcess("process_1")
        p1.start()
        p1.join()
        print("subprocess pid: %s" % p1.pid)
        print("current process pid: %s" % os.getpid())
    
     
    subprocess pid: 14772
    current process pid: 15120
    
     

    python多线程与多进程比较

     

    先来看两个例子:

    开启两个python线程分别做一亿次加一操作,和单独使用一个线程做一亿次加一操作:

    In [20]:
    def tstart(arg):
        var = 0
        for i in range(100000000):
            var += 1
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=tstart, args=('This is thread 1',))
        t2 = threading.Thread(target=tstart, args=('This is thread 2',))
        start_time = time.time()
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        print("Two thread cost time: %s" % (time.time() - start_time))
        start_time = time.time()
        tstart("This is thread 0")
        print("Main thread cost time: %s" % (time.time() - start_time))
    
     
    Two thread cost time: 7.077333211898804
    Main thread cost time: 3.5601541996002197
    
    In [21]:
    def tstart(arg):
        var = 0
        for i in range(100000000):
            var += 1
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=tstart, args=('This is thread 1',))
        t2 = threading.Thread(target=tstart, args=('This is thread 2',))
        start_time = time.time()
        t1.start()
        t1.join()
        print("Two thread cost time: %s" % (time.time() - start_time))
        start_time = time.time()
        tstart("This is thread 0")
        print("Main thread cost time: %s" % (time.time() - start_time))
    
     
    Two thread cost time: 3.669692039489746
    Main thread cost time: 3.430530309677124
    
     

    上面的例子如果只开启t1和t2两个线程中的一个,那么运行时间和主线程基本一致。 使用两个进程进行上面的操作:

    In [24]:
    def pstart(arg):
        var = 0
        for i in range(100000000):
            var += 1
    
    
    if __name__ == '__main__':
        p1 = Process(target=pstart, args=("1",))
        p2 = Process(target=pstart, args=("2",))
        start_time = time.time()
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        print("Two process cost time: %s" % (time.time() - start_time))
        start_time = time.time()
        pstart("0")
        print("Current process cost time: %s" % (time.time() - start_time))
    
     
    Two process cost time: 0.08162665367126465
    Current process cost time: 3.4535038471221924
    
     

    concurrent.futures.ThreadPoolExecutor并发库详解

     

    Python3.2带来的新版功能。是Python并发执行的标准库。

    这个模块具有线程池和进程池、管理并行编程任务、处理非确定性的执行流程、进程/线程同步等功能。

    concurrent.futures 是两个文件放在一起作为这个模块,因为concurrent文件夹下只有futures这个文件夹,而futures下有两个主要文件thread.py和process.py :

    深度理解,参见《Python并行编程 中文版》、《concurrent.futures官方文档》 ———————————————— 版权声明:本文为CSDN博主「quantLearner」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/The_Time_Runner/article/details/99652083

     
    1. 它可以解决大部分的复杂问题      【但并不是全部,如果尝试后效果不好,还需要使用他们的高级用法】

    2. 而且统一了线程和进程的用法

    concurrent.future 提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 两个类,其实是对 线程池和进程池 的进一步抽象,而且具有以下特点:

    1. 主程序可以获取子程序的状态和返回值

    2. 子程序完成时,主程序能立刻知道

     

    此模块由以下部分组成:

    concurrent.futures.Executor: 这是一个虚拟基类,提供了异步执行的方法。 submit(function, argument): 调度函数(可调用的对象)的执行,将 argument 作为参数传入。 map(function, argument): 将 argument 作为参数执行函数,以 异步 的方式。 shutdown(Wait=True): 发出让执行者释放所有资源的信号。 concurrent.futures.Future: 其中包括函数的异步执行。Future对象是submit任务(即带有参数的functions)到executor的实例。 Executor是抽象类,可以通过子类访问,即线程或进程的 ExecutorPools 。因为,线程或进程的实例是依赖于资源的任务,所以最好以“池”的形式将他们组织在一起,作为可以重用的launcher或executor。

     

    ThreadPoolExecutor

     

    ThreadPoolExecutor 是 Executor 的子类,它使用线程池来异步执行调用。

    concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())

    Executor 的一个子类,使用最多 max_workers 个线程的线程池来异步执行调用。如果 max_workers 为 None 或没有指定,将默认为机器处理器的个数。

     

    效率验证

     

    求最大公约数,测试数据如下

    In [27]:
    def gcd(pair):
        # 最大公约数
        a, b = pair
        low = min(a, b)
        for i in range(low, 0, -1):
            if a % i == 0 and b % i == 0:
                return i
    
    
    numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)]
    
     

    无并发

    In [33]:
    sum = 0
    for i in range(20):
        start = time.time()
        results = list(map(gcd, numbers))
        end = time.time()
        sum += end - start
    
    print(sum/20)          
    
     
    0.326509153842926
    
     

    多线程

    In [35]:
    from concurrent.futures import ThreadPoolExecutor
    sum = 0
    for i in range(20):
        start = time.time()
        pool = ThreadPoolExecutor(max_workers=10)
        results = list(pool.map(gcd, numbers))
        end = time.time()
        sum += end - start
    
    print(sum/20)              
    
     
    0.3289961576461792
    
     

    分析:由于全局解释器锁GIL的存在,多线程无法利用多核CPU进行并行计算,而是只使用了一个核,加上本身的开销,计算效率更低了。

    通过 资源管理器 查看 CPU 使用率:25%左右    【4核,用了一个】

     

    多进程

    In [43]:
    from concurrent.futures import ProcessPoolExecutor
    import time
    
    
    def gcd(pair):
        # 最大公约数
        a, b = pair
        low = min(a, b)
        for i in range(low, 0, -1):
            if a % i == 0 and b % i == 0:
                return i
    
    
    numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)]
    
    if __name__ == '__main__':
    
        sum = 0
        for i in range(20):
            start = time.time()
            pool2 = ProcessPoolExecutor(max_workers=3)
            results2 = list(pool2.map(gcd, numbers))
            end = time.time()
            sum += end - start
    
        print(sum / 20)  # 0.2696140885353088
    
     

    分析:利用多核CPU并行计算,比多线程快了点,但是由于本身的开销,还是没有无并发效率高,

    通过 资源管理器 查看 CPU 使用率:75%左右     【4核,用了三个,max_workers=3】

    这主要是数据量太小了,体现不出并发的优势,于是我把数据量稍微加大点

    numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)] * 10 重新测试,无并发 7s,多进程 2s,效果明显提高。

    注意,在使用多进程时,必须把 多进程代码 写在 if name == 'main' 下面,否则异常,甚至报错

    concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.

    小结:多线程不适合计算密集型,适合IO密集型,后面我会验证,多进程适合计算密集型。

    参考资料:

    https://www.jianshu.com/p/b9b3d66aa0be

  • 相关阅读:
    2015/8/28 回校正常学习工作
    Asp.net自定义控件开发任我行(3)-Render
    Asp.net自定义控件开发任我行(2)-TagPrefix标签
    Asp.net自定义控件开发任我行(1)-笑傲江湖
    ET采集阿里妈妈淘宝客商品规则
    淘宝API还能用来采集数据吗?taobao.item.get 接口还能用吗?
    淘宝api升级,无法采集淘宝的数据,taobao.item.get 和taobao.taobaoke.items.get都不能用
    用firefox浏览器访问localhost,apache http server 已停止工作
    淘宝客网站怎么批量采集淘宝商品,方维采集淘宝数据思路
    方维购物分享系统怎么样,方维系统安全性检测
  • 原文地址:https://www.cnblogs.com/xinmomoyan/p/15412478.html
Copyright © 2020-2023  润新知