【Python3 笔记】Python3 进程和线程 多进程 多线程 ThreadLocal 异步I/O 分布式进程

本系列是学习 廖雪峰 Python3 教程 过程中记录的笔记,本篇文章记录 Python 中的进程和线程的相关内容,主要包括多进程、多线程的实现,ThreadLocal 的使用,以及对异步 I/O 和 分布式进程的介绍。

进程和线程

  • 线程是最小的执行单元,而进程由至少一个线程组成。
  • 如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
  • 多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。

多进程

  • Unix/Linux/Mac 下,可以使用 fork() 调用实现多进程,multiprocessing 模块可实现跨平台的多进程;

  • 启动几个子进程,使用 Process 模块 Process(target=func, args=(,))

  • 启动大量子进程,用进程池 Pool 批量创建,chi.apply_async(func=func, args=(,)

  • 进程间通信是通过 QueuePipes 等实现的;

    """Process()创建子进程 记录子进程启动的时间戳"""
    import time
    from multiprocessing import Process
    
    def action(name):  # 待会两个进程要执行的任务↓
        content = name
        for i in range(3):  # 循环3次
            content += str(time.time()) + ' -> '
            time.sleep(0.2)  # 等待0.2s
        print(content)
    
    if __name__ == '__main__':  # 这行代码很重要,新建进程的时候都加上它!!
    
        jc1 = Process(target=action, args=('进程一 时间戳:',))  # 准备建立一个进程:multiprocessing.Process()
        jc2 = Process(target=action, args=('进程二 时间戳:',))  # 再准备建立一个新进程,这是基本格式记住←
    
        jc1.start()  # 将蓄势待发的jc1进程正式启动!!
        jc2.start()  # 同上...
    
        jc1.join()  # 等待进程jc1将任务执行完...
        jc2.join()  # ...
        print('来自主进程:jc1,jc2任务都已执行完毕')
    
        jc1.close()  # 彻底关闭进程jc1
        jc2.close()  # ...
        
    output——————————————————————
    进程一 时间戳:1567329729.133326 -> 1567329729.3337915 -> 1567329729.534282 -> 
    进程二 时间戳:1567329729.1503086 -> 1567329729.3507462 -> 1567329729.5512364 -> 
    来自主进程:jc1,jc2任务都已执行完毕
    
    """Pool() 创建多个子进程,记录每次调用时分配的进程id"""
    import time, os
    from multiprocessing import Pool
    
    def action1(name, b=3):
        content = name
        for i in range(b):
            content += str(i) + " pid: " + str(os.getpid())
            time.sleep(0.1)
        print(content)
    
    if __name__ == '__main__':  # 还要添加这行,否则可能出现异常
        chi = Pool(2)            # 同时执行的最大进程数,并非进程池的容量
        chi.apply_async(func=action1, args=('进程一',))     # 启动第一个子进程...
        chi.apply_async(func=action1, args=('进程二', 3))  # 和普通进程的启动方式有很大不同仔细看
        chi.apply_async(func=action1, args=('进程三', 4))  # Pool的最基本格式记住←
        # 注意:程序现在有4个进程在运行:上面的三个子进程 和一个最为核心的:主进程
    
        chi.close()  # 关闭进程池(但池子内已启动的子进程还会继续进行)
        chi.join()   # 等待进程池内的所有子进程完毕
        print('子进程全部结束,主进程在此!')
        
    output——————————————————————————
    进程一0 pid: 166801 pid: 166802 pid: 16680
    进程二0 pid: 161161 pid: 161162 pid: 16116
    进程三0 pid: 166801 pid: 166802 pid: 166803 pid: 16680
    子进程全部结束,主进程在此!
    
    """使用Queue() 完成子进程和主进程之间的通信"""
    from multiprocessing import Queue, Process
    
    def foo(aa):
        mes = aa.get()  # 管子的另一端放在子进程这里,等待接收数据,【阻塞】
        print('子进程——', mes)
        aa.put("收到!已经中止。")
    
    if __name__ == '__main__':  # 要加这行...
        tx = Queue()  # 创建进程通信的Queue,你可以理解为我拿了个管子来...
        jc = Process(target=foo, args=(tx,))  # 创建子进程
        jc.start()  # 启子子进程
    
        print('主进程准备发送数据...')
        tx.put('有内鬼,终止交易!')  # 将管子的一端放在主进程这里,主进程往管子里丢入数据↑
        jc.join()  # 等待子进程中止
        message = tx.get()
        print('主进程——', message)
        
    output——————————————————————————
    主进程准备发送数据...
    子进程—— 有内鬼,终止交易!
    主进程—— 收到!已经中止。
    

多线程

  • 使用 threading.Thread(target=run_thread, args=(,)) 来创建新线程;

  • 使用 threading.Lock() 来创建锁,操作共享变量前申请获得、用完后一定要释放;

  • 多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生;

  • Python 解释器由于设计时有 GIL 全局锁,导致了多线程无法利用多核。多线程的并发在 Python 中就是一个美丽的梦。

    import threading
    
    balance = 0
    def change_it(n):
        # 先存后取,结果应该为0
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        lock = threading.Lock()
        for i in range(100000):
            lock.acquire()  # 先要获取锁
            try:
                change_it(n)  # 放心地改吧
            finally:
                lock.release()  # 改完了一定要释放锁
    
    def main():
        t1 = threading.Thread(target=run_thread, args=(5,))
        t2 = threading.Thread(target=run_thread, args=(8,))
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        print('total balance is :', balance)
    
    if __name__ == '__main__':
        main()
        
    output——————————————————————————
    total balance is : 0
    

ThreadLocal

  • 调用 threading.local() 实现,解决了参数在一个线程中各个函数之间互相传递的问题;

  • 一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰;

    import threading
    
    # 创建全局ThreadLocal对象:
    local_school = threading.local()
    
    def process_student():
        # 获取当前线程关联的student:
        std = local_school.student
        print('Hello, %s (in %s)' % (std, threading.current_thread().name))
    
    def process_thread(name):
        # 绑定ThreadLocal的student:
        local_school.student = name
        process_student()
    
    
    t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
    t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-B')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    output——————————————————————
    Hello, Alice (in Thread-A)
    Hello, Bob (in Thread-B)
    

异步 I/O 和 分布式进程

  • 单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序;
  • 事件驱动模型:充分利用操作系统提供的异步IO支持,用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型;
  • 对于进程和线程,优先选择 Process ,更稳定且可以部署到多台机器,Thread 最多只能分布到同一台机器的多个 CPU 上;
  • Python 的分布式进程接口简单,封装良好,适合需要把繁重任务分布到多台机器的环境下;
  • 注意 Queue 的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小,减轻通信的负担;
  • 分布式进程详见 这里 ,另外,分布式进程之间怎么协作呢?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值