Day11-进程和线程

文章目录

    day11-进程和线程

    1. 进程

    进程:进行中的应用程序 属于一个程序的运行状态 属于CPU分配资源的最小单位

    2. 线程

    一个进程至少有一个线程 否则无法执行 属于CPU调度执行的最小单位

    3. 进程和线程的执行机制

    单核心场景下 进程以及线程 都是"随机"轮流交替执行的 CPU调度执行各个进程或者线程参考的条件因素非常多

    CPU时间片轮 优先等级(进程或者线程) 调度策略 等等

    CPU执行多个任务,每个任务执行的时间非常短暂,以毫秒为单位,所以宏观上看上去,像是同时在执行

    微观的角度,随机轮流交替执行的

    4. 并发和并行

    并发:同时发生,轮流交替执行

    并行:真正意义上的同时执行

    5. 线程或者进程不是越多越好

    要结合实际的硬件环境

    6. 同步和异步

    同步:多个线程有排队机制的(顺序通常也无法保证),效率比较低,但是可以保证数据安全

    异步:各个线程各自执行,没有先后顺序,没有排队机制,效率高,但是数据不安全

    7. 进程的使用

    7.1 进程创建方式1

    实现多进程方式1:使用multiprocessing.Process类 创建进程 观察两个进程轮流交替执行的过程

    关于打印结果的问题: ​ 1.多个进程是轮流交替执行的 先后顺序无法保证 ​ 2.多条输出语句并行打印 因为打印的时候文字和换行是分开打印的 所以会出现这种现象 ​ 3.程序结束在最后一行 却最先打印的问题 ​ 因为这条输出语句有main进程负责执行的 执行的内容相对较少 所以大概率先执行完 ​ 如果需要子进程执行完毕再执行主进程 则可以使用 join函数

    """
        实现多进程方式1:使用multiprocessing.Process类
        创建进程 观察两个进程轮流交替执行的过程
    
        关于打印结果的问题:
            1.多个进程是轮流交替执行的 先后顺序无法保证
            2.多条输出语句并行打印 因为打印的时候文字和换行是分开打印的 所以会出现这种现象
            3.程序结束在最后一行 却最先打印的问题
                因为这条输出语句有main进程负责执行的 执行的内容相对较少 所以大概率先执行完
                如果需要子进程执行完毕再执行主进程 则可以使用 join函数
    
    """
    
    import multiprocessing as mp
    import os
    import time
    
    
    # 编写函数 用于进程所执行的任务
    def  task():
        for i in range(10):
            print(f"进程名称:{mp.current_process().name},进程ID{os.getpid()}执行任务:{i}")
            time.sleep(0.1)
    
    
    
    if __name__ == '__main__':
        p1 = mp.Process(target = task , name = "进程A")
        p2 = mp.Process(target = task , name = "******进程B******")
    
        p1.start() # 这里表示进程就绪
        p2.start() # 这里表示进程就绪
    
        p1.join() # 表示主进程等待当前进程运行完毕 再继续运行
        p2.join() # 表示主进程等待当前进程运行完毕 再继续运行
    
    
        print("程序结束")
    
    
    
    

    7.2 进程创建方式2

    实现多进程方式2:继承multiprocessing.Process类 ​ init__函数 如果在本类中编写了__init 那么必须将父类的__init__重新调用 ​ 因为如果不调用 则表示覆盖了父类的实现 而父类中的实现默认会初始化很多信息 ​ 所以一旦覆盖就会报错了

    重写run方法 run方法用于编写进程需要执行的内容

    面试题:调用start和调用run的区别? 调用start属于向CPU发出信息 表示当前进程准备就绪 可以被执行 等待CPU在合适的时机 开辟进程并且执行这个进程 直接调用run不会开辟进程 直接由主进程MainProcess类执行run函数

    """
        实现多进程方式2:继承multiprocessing.Process类
            __init__函数 如果在本类中编写了__init__ 那么必须将父类的__init__重新调用
            因为如果不调用 则表示覆盖了父类的实现 而父类中的实现默认会初始化很多信息
            所以一旦覆盖就会报错了
    
    
            重写run方法 run方法用于编写进程需要执行的内容
    
    
            面试题:调用start和调用run的区别?
            调用start属于向CPU发出信息 表示当前进程准备就绪 可以被执行 等待CPU在合适的时机 开辟进程并且执行这个进程
            直接调用run不会开辟进程 直接由主进程MainProcess类执行run函数 
    """
    
    
    import multiprocessing as mp
    import time
    import os
    
    class MyProcess(mp.Process):
        def __init__(self,name):
            super().__init__() # 必须调用父类的__init__
            self.name = name # 创建当前类对象 可以传入一个name参数 用于指定进程的名字
    
    
        def run(self):
            """
                用于记录进程要执行的内容
            :return:
            """
            for i in range(10):
                name = mp.current_process().name
                pid = os.getpid()
                print(f"进程名称:{name},进程id{pid},执行任务{i}")
                time.sleep(0.2)
    
    
    if __name__ == "__main__":
        p1 = MyProcess("******进程A******")
        p2 = MyProcess("进程B")
    
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    
        # p1.run()
        # p2.run()
    
        print("程序结束")
    
    

    7.3 进程创建方式3

    创建进程方式3:进程池的方式 进程池可以自行管理我们创建的进程 提高进程的复用性

    """
        创建进程方式3:进程池的方式
    
    """
    
    from multiprocessing import Pool
    import os
    import time
    
    
    def task(task_name):
        print(f"进程池执行任务{task_name},进程id{os.getpid()}正在执行")
        time.sleep(0.2)
        print(f"进程池执行任务{task_name},进程id{os.getpid()}执行完毕")
    
    
    if __name__ == '__main__':
        pool = Pool(3) # 创建3个进程
    
        for i in range(10):
            pool.apply(func = task,args=(i,)) # 同步的方式执行
            # pool.apply_async(func = task,args=(i,)) # 异步的方式执行
    
    
        pool.close() #表示不再往池中添加任务了
    
        pool.join()
    
        print("程序结束")
    
    

    7.4 进程不共享全局变量

    进程之间默认是不共享全局变量 即每个进程独自访问一份全局变量 多个进程访问多份 互不影响

    """
        进程之间默认是不共享全局变量 即每个进程独自访问一份全局变量
        多个进程访问多份 互不影响
    """
    import os
    from multiprocessing import Process
    import time
    
    def func(build_list):
        for i in range(10):
            build_list.append(i)
            print(f"进程id:{os.getpid()}",build_list)
            time.sleep(0.1)
    
    
    
    if __name__ == '__main__':
    
        list1 = []
    
        p1 = Process(target=func , name = "进程A" ,args=(list1,))
        p2 = Process(target=func , name = "*****进程B*****" ,args=(list1,))
    
        p1.start()
        p2.start()
    
        p1.join()
        p2.join()
    
        print("程序结束")
    

    8. 线程

    8.1 线程创建方式1

    创建线程方式1:threading.Thread 删除线 说明这个API被弃用了 表示不推荐使用 通常是有更好的解决方案了 注意:已弃用并不是删除了 不能用了 因为还要兼顾一些老的系统中使用 只是提醒我们 不推荐使用了

    """
        创建线程方式1:threading.Thread
        删除线 说明这个API被弃用了 表示不推荐使用 通常是有更好的解决方案了
        注意:已弃用并不是删除了 不能用了 因为还要兼顾一些老的系统中使用
        只是提醒我们 不推荐使用了
    """
    
    import threading as td
    import time
    
    
    def run():
        for i in range(10):
            print(f"线程名称{td.current_thread().name}打印数据:{i}")
            time.sleep(0.1)
    
    if __name__ == '__main__':
        t1 = td.Thread(target=run,name="******线程A******")
        t2 = td.Thread(target=run,name="线程B")
    
        t1.start()
        t2.start()
    
        t1.join()
        t2.join()
    
        print("程序结束")
    
    
    
    
    

    8.2 线程创建方式2

    创建线程方式2:编写子类继承Thread父类

    """
        创建线程方式2:编写子类继承Thread父类
    """
    
    from threading import Thread
    import time
    
    
    class MyThread(Thread):
        def __init__(self,name):
            super().__init__()
            self.name = name
    
        def run(self):
            for i in range(10):
                print(f"线程名称:{self.name}打印数据{i}")
                time.sleep(0.1)
    
    if __name__ == '__main__':
        t1 = MyThread("*****线程A*****")
        t2 = MyThread("线程B")
    
    
        t1.start()
        t2.start()
    
        t1.join()
        t2.join()
    
        print("程序结束")
    
    
    
    

    8.3 线程创建方式3

    创建线程方式3:线程池的方式

    """
        创建线程方式3:线程池的方式
    """
    import time
    from concurrent.futures import ThreadPoolExecutor
    import threading as td
    
    def task():
        print(f"线程名称:{td.current_thread().name}开始执行任务")
        time.sleep(1)
        print(f"线程名称:{td.current_thread().name}开始任务完毕")
    
    
    if __name__ == '__main__':
        with ThreadPoolExecutor(max_workers=2) as pool:
            for i in range(10):
                pool.submit(task)
    
    
        print("程序结束")
    
    
    
    
    
    
    
    

    9. 线程安全问题

    使用多线程模拟多人抢票 线程安全的问题 三个人抢10张票 不能超卖 不能重复卖出

    互斥锁(同步锁) 可重入锁 不可重入锁 自旋锁 无锁

    """
        使用多线程模拟多人抢票 线程安全的问题
        三个人抢10张票 不能超卖 不能重复卖出
    
        互斥锁(同步锁)
        可重入锁 不可重入锁 自旋锁 无锁
    
    """
    
    import threading as td
    import time
    
    tickets = 10 # 总票数 10张
    lock = td.Lock() # 创建锁对象
    
    def buy_ticket():
        global tickets
        while True:
            time.sleep(1)
            lock.acquire() # 上锁 从这里开始只允许一个线程访问
    
            if tickets == 0:
                lock.release()
                break # 结束循环
            tickets -= 1 # 票数-1
            print(f"{td.current_thread().name}抢到了第{10 - tickets}张票,还剩余{tickets}张票")
    
            lock.release() # 释放锁 其他线程可以再次从上方进入代码体执行
    
        print("票卖完了")
    
    if __name__ == "__main__":
        t1 = td.Thread(target=buy_ticket,name="赵四")
        t2 = td.Thread(target=buy_ticket,name="小宝")
        t3 = td.Thread(target=buy_ticket,name="大拿")
    
    
        t1.start()
        t2.start()
        t3.start()
    
        t1.join()
        t2.join()
        t3.join()
    
        print("程序结束")
    

    10. GIL

    GIL全局解释器锁 是一个锁 同时只允许一个线程执行 所以Python中的多线程 不是真正意义上的多线程 为什么Python使用全局解释器锁 限制了硬件的能力 ​ 1.历史原因 Python诞生于90年代 硬件环境不支持多核 ​ 2.早期的C语言 作为Python的底层代码 本身线程是不安全的 Python为了解决线程安全这个问题 ​ 所以在加了GIL全局解释器锁(一把大锁) ​ 3.虽然GIL限制了Python多线程的能力,但是Python能处理的任务大多数是不需要多线程并发执行的

    Python擅长处理什么类型的任务?IO密集型的任务 ​ A CPU密集的任务 需要做大量的计算 比如图形解析 复杂算法等等 ​ B IO密集型任务 比如网络请求 文件读写 数据库操作 脚本开发 AI机器学习

    """
        GIL全局解释器锁
        是一个锁 同时只允许一个线程执行 所以Python中的多线程 不是真正意义上的多线程
        为什么Python使用全局解释器锁 限制了硬件的能力
            1.历史原因 Python诞生于90年代 硬件环境不支持多核
            2.早期的C语言 作为Python的底层代码 本身线程是不安全的 Python为了解决线程安全这个问题
            所以在加了GIL全局解释器锁(一把大锁)
            3.虽然GIL限制了Python多线程的能力,但是Python能处理的任务大多数是不需要多线程并发执行的
    
    
        Python擅长处理什么类型的任务?IO密集型的任务
            A CPU密集的任务 需要做大量的计算 比如图形解析 复杂算法等等
            B IO密集型任务 比如网络请求 文件读写 数据库操作 脚本开发 AI机器学习
    
    """
    
    
    import threading
    import multiprocessing
    import time
    
    def task():
        count = 0
        while count < 100000000:
            count += 1
    
    if __name__ == '__main__':
        start = time.time()
        t1 = threading.Thread(target=task)
        t2 = threading.Thread(target=task)
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        end = time.time()
        print(f"多线程耗时:{end - start :.2f}s")
    
        start = time.time()
        p1 = multiprocessing.Process(target=task) # 每个进程各自使用一个单独GIL
        p2 = multiprocessing.Process(target=task) # 每个进程各自使用一个单独GIL
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        end = time.time()
        print(f"多进程耗时:{end - start :.2f}s")
    
    
    
    
    
    end