• Python多任务编程


    1.进程与多任务

    1. 1 多任务的介绍

    1.使用多任务能充分利用CPU资源,提高程序的执行效率,让程序具备处理多任务的能力。

    2.多任务执行方式有两种:

    并发:在一段时间内交替执行多个任务。

    并行:在一段时间内真正的同时一起执行多个任务。

    1.2 进程的介绍

    1.进程(Process)是资源分配的最小单位

    2.多进程Python程序实现多任务的一种方式,使用多进程可以大大提高程序的执行效率。

    1.3 多进程完成多任务

    1.导入进程包。

    2.创建子进程并指定执行的任务。

    3.启动进程执行任务。

    1. # 导入进程模块
    2. import multiprocessing
    3. import time
    4. # 敲代码
    5. def coding():
    6. for i in range(3):
    7. print("coding...")
    8. time.sleep(0.2)
    9. # 听音乐
    10. def music():
    11. for i in range(3):
    12. print("music...")
    13. time.sleep(0.2)
    14. if __name__ == '__main__':
    15. # coding()
    16. # music()
    17. # 通过进程类创建进程对象
    18. coding_process = multiprocessing.Process(target=coding)
    19. music_process = multiprocessing.Process(target=music)
    20. # 启动进程
    21. coding_process.start()
    22. music_process.start()

    1.4 进程执行带有参数的任务

    进程执行带有参数的任务有两种方式:

    1.元组方式传参:元组方式传参一定要和参数的顺序保持一致。

    2.字典方式传参:字典方式传参字典中的key一定要和参数名保持一致。

    1. # 导入进程模块
    2. import multiprocessing
    3. import time
    4. # 敲代码
    5. def coding(num,name):
    6. for i in range(num):
    7. print(name)
    8. print("coding...")
    9. time.sleep(0.2)
    10. # 听音乐
    11. def music(count):
    12. for i in range(count):
    13. print("music...")
    14. time.sleep(0.2)
    15. if __name__ == '__main__':
    16. # coding()
    17. # music()
    18. # 通过进程类创建进程对象
    19. coding_process = multiprocessing.Process(target=coding,args=(3,"zs"))
    20. music_process = multiprocessing.Process(target=music,kwargs={"count":2})
    21. # 启动进程
    22. coding_process.start()
    23. music_process.start()

    1.5 获取进程编号

    1.获取当前进程编号

    os.getpid()

    2.获取当前父进程编号

    os.getppid()

    1. # 导入进程模块
    2. import multiprocessing
    3. import os
    4. import time
    5. # 敲代码
    6. def coding():
    7. # 获取coding_process的编号
    8. print("coding_process>>>%d" % os.getpid())
    9. # 获取coding_process的父进程的编号
    10. print("coding_process的父进程>>>%d" % os.getppid())
    11. for i in range(3):
    12. print("coding...")
    13. time.sleep(0.2)
    14. # 听音乐
    15. def music():
    16. # 获取music_process的编号
    17. print("music_process>>>%d" % os.getpid())
    18. # 获取music_process的父进程的编号
    19. print("music_process的父进程>>>%d" % os.getppid())
    20. for i in range(3):
    21. print("music...")
    22. time.sleep(0.2)
    23. if __name__ == '__main__':
    24. # 获取主进程的编号
    25. print("主进程>>>%d" % os.getpid())
    26. # 通过进程类创建进程对象
    27. coding_process = multiprocessing.Process(target=coding)
    28. music_process = multiprocessing.Process(target=music)
    29. # 启动进程
    30. coding_process.start()
    31. music_process.start()

    两个子进程的父进程的编号与主进程的编号相同,证明两个子进程是由该父进程创建并启动的。

    1.6 进程间不共享全局变量

    • 创建子进程会对主进程资源进行拷贝,也就是子进程就是主进程的一个副本。
    • 进程间不共享全局变量,因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
    1. import multiprocessing
    2. import time
    3. # 全局变量
    4. my_list = []
    5. # 写入数据
    6. def write_data():
    7. for i in range(3):
    8. my_list.append(i)
    9. print("add:",i)
    10. print("write_data:",my_list)
    11. # 读取数据
    12. def read_data():
    13. print("read_data:",my_list)
    14. if __name__ == '__main__':
    15. # 创建写入数据进程
    16. write_process = multiprocessing.Process(target=write_data)
    17. # 创建读取数据进程
    18. read_process = multiprocessing.Process(target=read_data)
    19. # 启动进程,执行任务
    20. write_process.start()
    21. time.sleep(1)
    22. read_process.start()

    写入数据进程和读取数据进程不共享my_list这个全局变量。 

    1.7 主进程和子进程的结束顺序

    默认情况下,为了保证子进程能够正常的运行,主进程会等待所有的子进程执行完后再结束。

    如果想要让主进程结束后子进程就销毁,可以采取以下两种方式:

    • 设置守护主进程方式:子进程对象.daemon = True
    • 销毁子进程方式:子进程对象.terminate()
    1. import multiprocessing
    2. import time
    3. # 工作函数
    4. def work():
    5. for i in range(10):
    6. print("工作中...")
    7. time.sleep(0.2)
    8. if __name__ == '__main__':
    9. # 创建子进程
    10. work_process = multiprocessing.Process(target=work)
    11. # # 方式一:设置守护主进程
    12. # work_process.daemon = True
    13. # 启动子进程
    14. work_process.start()
    15. # 延时1秒
    16. time.sleep(1)
    17. # 方式二:手动销毁子进程
    18. work_process.terminate()
    19. print("主进程执行完毕")

    2. 线程与多任务

    2.1 线程的介绍

    1.多线程是Python程序中实现多任务的一种方式。

    2.线程是程序执行的最小单位

    3.同属一个进程的多个线程共享进程所拥有的全部资源

    2.2 多线程完成多任务

    1.导入线程模块

    2.创建子线程

    3.启动线程执行任务

    1. import time
    2. import threading
    3. # 敲代码
    4. def coding():
    5. for i in range(3):
    6. print("coding...")
    7. time.sleep(0.2)
    8. # 听音乐
    9. def music():
    10. for i in range(3):
    11. print("music...")
    12. time.sleep(0.2)
    13. if __name__ == '__main__':
    14. # 创建子线程
    15. coding_thread = threading.Thread(target=coding)
    16. music_thread = threading.Thread(target=music)
    17. # 启动线程执行任务
    18. coding_thread.start()
    19. music_thread.start()

    2.3 线程执行带有参数的任务

    线程执行带有参数的任务有两种方式:

    1.元组方式传参:元组方式传参一定要和参数的顺序保持一致。

    2.字典方式传参:字典方式传参字典中的key一定要和参数名保持一致。

    1. import time
    2. import threading
    3. # 敲代码
    4. def coding(num):
    5. for i in range(num):
    6. print("coding...")
    7. time.sleep(0.2)
    8. # 听音乐
    9. def music(count):
    10. for i in range(count):
    11. print("music...")
    12. time.sleep(0.2)
    13. if __name__ == '__main__':
    14. # 创建子线程
    15. coding_thread = threading.Thread(target=coding,args=(3,))
    16. music_thread = threading.Thread(target=music,kwargs={"count" : 2})
    17. # 启动线程执行任务
    18. coding_thread.start()
    19. music_thread.start()

    2.4 主线程和子线程的结束顺序

    默认情况下,为了保证子线程够正常的运行,主线程会等待所有的子线程执行完后再结束。

    如果想要让主线程结束后子线程就销毁,可以采取以下两种方式:

    • 方式一:参数方式设置守护主线程
    • 方式二:方法方式设置守护主线程
    1. import time
    2. import threading
    3. # 工作函数
    4. def work():
    5. for i in range(10):
    6. print("work...")
    7. time.sleep(0.2)
    8. if __name__ == '__main__':
    9. # 创建线程
    10. # 方式一:参数方式设置守护主线程
    11. # work_thread = threading.Thread(target=work,daemon=True)
    12. work_thread = threading.Thread(target=work)
    13. # 方式二:方法方式设置守护主线程
    14. work_thread.setDaemon(True)
    15. # 启动线程执行任务
    16. work_thread.start()
    17. # 延时1秒
    18. time.sleep(1)
    19. print("主线程执行完毕")

    2.5 线程间的执行顺序

     线程之间执行是无序的,是由CPU调度决定某个线程先执行的。

    1. import threading
    2. import time
    3. # 获取线程信息函数
    4. def get_info():
    5. time.sleep(0.5)
    6. # 获取线程信息
    7. current_thread = threading.current_thread()
    8. print(current_thread)
    9. if __name__ == '__main__':
    10. for i in range(10):
    11. # 创建子线程
    12. sub_thread = threading.Thread(target=get_info)
    13. # 启动线程执行任务
    14. sub_thread.start()

    2.6 线程间共享全局变量

    1. import threading
    2. import time
    3. # 全局变量
    4. my_list = []
    5. # 写入数据
    6. def write_data():
    7. for i in range(3):
    8. my_list.append(i)
    9. print("add:",i)
    10. print("write_data:",my_list)
    11. # 读取数据
    12. def read_data():
    13. print("read_data:",my_list)
    14. if __name__ == '__main__':
    15. # 创建写入数据线程
    16. write_thread = threading.Thread(target=write_data)
    17. # 创建读取数据线程
    18. read_thread = threading.Thread(target=read_data)
    19. # 启动线程,执行任务
    20. write_thread.start()
    21. time.sleep(1)
    22. read_thread.start()

    线程间共享my_list这个共享变量。

    2.7 线程间资源竞争问题

    多线程同时操作全局变量可能会导致数据出现错误,可以使用线程同步方式来解决这个问题。

    线程同步方式:

    • 互斥锁
    1. import threading
    2. # 全局变量
    3. g_num = 0
    4. # 对g_num进行加操作
    5. def sum_num1():
    6. for i in range(1000000):
    7. # 声明全局变量
    8. global g_num
    9. g_num += 1
    10. print("g_num1:",g_num)
    11. # 对g_num进行加操作
    12. def sum_num2():
    13. for i in range(1000000):
    14. # 声明全局变量
    15. global g_num
    16. g_num += 1
    17. print("g_num2:",g_num)
    18. if __name__ == '__main__':
    19. # 创建子线程
    20. sum1_thread = threading.Thread(target=sum_num1)
    21. sum2_thread = threading.Thread(target=sum_num2)
    22. # 启动线程
    23. sum1_thread.start()
    24. sum2_thread.start()

    2.8 互斥锁的使用

    1.互斥锁的使用

    threading.Lock()

    2.上锁

    mutex.acquire()

    3.解锁

    mutex.release()

    1. import threading
    2. # 全局变量
    3. g_num = 0
    4. # 对g_num进行加操作
    5. def sum_num1():
    6. # 上锁
    7. mutex.acquire()
    8. for i in range(1000000):
    9. # 声明全局变量
    10. global g_num
    11. g_num += 1
    12. # 解锁
    13. mutex.release()
    14. print("g_num1:",g_num)
    15. # 对g_num进行加操作
    16. def sum_num2():
    17. # 上锁
    18. mutex.acquire()
    19. for i in range(1000000):
    20. # 声明全局变量
    21. global g_num
    22. g_num += 1
    23. # 解锁
    24. mutex.release()
    25. print("g_num2:",g_num)
    26. if __name__ == '__main__':
    27. # 互斥锁的创建
    28. mutex = threading.Lock()
    29. # 创建子线程
    30. sum1_thread = threading.Thread(target=sum_num1)
    31. sum2_thread = threading.Thread(target=sum_num2)
    32. # 启动线程
    33. sum1_thread.start()
    34. sum2_thread.start()

     

    2.9 死锁

    死锁:一直等待对方释放锁的情景就是死锁。

    死锁的结果:会造成应用程序停止响应,不能再处理其它任务了。

    死锁的注意点:

    • 使用互斥锁的时候需要注意死锁的问题,在合适的地方注意释放锁。
    • 死锁一旦产生就会造成应用程序的停止响应,应用程序无法继续往下执行了。
    1. import threading
    2. # 全局变量
    3. g_num = 0
    4. # 对g_num进行加操作
    5. def sum_num1():
    6. print("sum_num1...")
    7. # 上锁
    8. mutex.acquire()
    9. for i in range(1000000):
    10. # 声明全局变量
    11. global g_num
    12. g_num += 1
    13. print("g_num1:",g_num)
    14. # 对g_num进行加操作
    15. def sum_num2():
    16. print("sum_num2...")
    17. # 上锁
    18. mutex.acquire()
    19. for i in range(1000000):
    20. # 声明全局变量
    21. global g_num
    22. g_num += 1
    23. print("g_num2:",g_num)
    24. if __name__ == '__main__':
    25. # 互斥锁的创建
    26. mutex = threading.Lock()
    27. # 创建子线程
    28. sum1_thread = threading.Thread(target=sum_num1)
    29. sum2_thread = threading.Thread(target=sum_num2)
    30. # 启动线程
    31. sum1_thread.start()
    32. sum2_thread.start()

    线程sum1_thread占用锁资源,而线程sum2_thread请求锁资源,导致死锁,线程无法继续运行下去。

    2.10 进程和线程的对比

    2.10.1 关系对比

    线程是依附在进程里面的,没有进程就没有线程。

    一个进程默认提供一条线程,当然进程可以创建多个线程。

    2.10.2 区别对比

    进程之间不共享全局变量,而线程之间共享全局变量(但是要注意资源竞争的问题)。

    创建进程的资源开销大于线程。

    进程是OS资源分配的基本单位,线程是CPU调度的基本单位。

    线程不能独立执行,必须依存在进程中。

    2.10.3 优缺点

    1.进程优缺点:

    优点:可以用多核(多个进程并行执行)。

    缺点:资源开销大。

    2.线程优缺点:

    优点:资源开销小。

    缺点:只能使用单核(多个线程并发执行)。

  • 相关阅读:
    gitHub不能用密码推送了,必须要使用令牌
    MYSQL的面试题,超级详细一定要看
    Thinkphp 5.0.24变量覆盖漏洞导致RCE分析
    入坑 Node.js 1
    Leetcode 49.字母异位词分组
    vue中自定义指令的使用场景及示例
    Docker设置开机自启动
    Navicat 连接 SQL Server 报错(IM002 未发现数据源名称并且未指定默认驱动程序)
    实战Kafka的部署
    赋能伙伴,聚势共赢!麒麟信安培训认证平台正式上线
  • 原文地址:https://blog.csdn.net/m0_60121089/article/details/127872850