python并发编程
多任务
在同一时间内执行多个任务
表现形式
- 并发:在一段时间内,交替执行任务
- 并行:在一段时间内,真正的同时一起执行多个任务
并发和并行的区别
- 并发:多个任务同时请求执行,但是在同一瞬间,CPU只能执行一个任务,于是安排它们交替执行,看起来好像是同时执行的,其实并不是,CPU在作者高效的切换
- 并行:多个人同时执行,前提是需要多核CPU
多任务的优势
能够充分利用CPU资源,提高程序执行效率
进程
进程是CPU资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位
通俗理解:一个正在运行的程序就是一个进程
多进程的作用
使用多进程可以打打提高程序的执行效率
python中多进程的基本工作方式
程序运行起来形成主进程,在主进程上创建子进程
多进程的代码实现
- 引入进程工具包
- 通过进程类实例化进程对象
- 启动进程执行任务
import multiprocessing子进程对象 = multiprocessing.Process()
子进程对象.start()
参数详解
multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={})
- group:参数未使用,值始终为None
- target: 表示调用对象,即子进程要执行的任务(回调函数入口地址)
- args: 表示以元组的形式向子任务函数传参,元组方式传参一定要和参数的顺序保持一致
- kwargs:表示以字典的方式给子任务函数传参,字典方式传参字典中的key要和参数名保持一致
- name: 子进程的名称
无参数模拟
# 使用多进程模拟一遍写代码,一遍听音乐import multiprocessing
import timedef coding():for i in range(3):print("正在写代码...")time.sleep(0.2)def music():for i in range(3):print("正在听音乐...")time.sleep(0.2)if __name__ == "__main__":# 创建进程对象coding_process = multiprocessing.Process(target=coding)music_process = multiprocessing.Process(target=music)# 启动进程coding_process.start()music_process.start()
带参数模拟
import multiprocessing
import timedef coding(name,num):for i in range(num):print(f"{name}正在写第{i}行代码...")time.sleep(0.1)def music(name,count):for i in range(count):print(f"{name}正在听第{i}首音乐...")time.sleep(0.1)if __name__ == "__main__":# 创建进程对象coding_process = multiprocessing.Process(target=coding,name='刘亦菲',args=("小明",10))music_process = multiprocessing.Process(target=music,name='胡歌',kwargs={"name":"小红","count":20})print(f"coding_process进程的名字是:{coding_process.name}")print(f"music_process进程的名字是:{music_process.name}")# 启动进程# 启动进程coding_process.start()music_process.start()
获取进程编号
进程编号的作用
进程编号唯一标识一个进程,方便管理进程。在一个操作系统中,一个进程拥有的进程号是唯一的,进程号可以反复使用。获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的
获取当前进程的编号
os.getpid()
import os
# 获取当前进程的PID
pid = os.getpid()
print(pid)
multiprocessing.current_process().pid
import multiprocessing
pid = multiprocessing.current_process().pid
print(pid)
获取父进程编号
import os
ppid = os.getppid()
print(ppid)
综合案例
import os
import multiprocessingprint(f"当前进程的pid是:{os.getpid()},{multiprocessing.current_process().pid},它的父进程id是:{os.getppid()}")
进程的注意介绍
- 进程之间不共享全局变量
- 主进程会等待所有的子进程执行结束后再结束
例:在不同进程中修改里表s_list[] 并新增元素,试着在各个进程中观察列表的最终结果
import multiprocessing
import times_list = []def write_data():for i in range(3):s_list.append(i)print(f"add:{i}")print(f"write_data:{s_list}")def read_data():print(f"read_data:{s_list}")if __name__ == "__main__":# 创建进程对象write_process = multiprocessing.Process(target=write_data)read_process = multiprocessing.Process(target=read_data)# 启动进程write_process.start() # 等待写进程执行完毕time.sleep(1)read_process.start() """
add:0
add:1
add:2
write_data:[0, 1, 2]
read_data:[]
"""
执行结果细节:
- 进程之间数据是相互隔离的,不能共享
- 子进程相当于父进程的副本,即 把父进程的内容拷贝一遍,单独执行
主进程-子进程同步结束
默认情况下,主进程会等待所有子进程执行结束后再结束,如果不让主进程等待子进程,方法如下
- 子进程设置守护进程
import multiprocessing
import timedef work():for i in range(10):print("工作中...")time.sleep(0.2)if __name__ == "__main__":# 创建子进程sub_process = multiprocessing.Process(target=work)# 设置守护主进程,主进程退出子进程直接销毁,不再执行子进程中的代码sub_process.daemon = Truesub_process.start()time.sleep(1)print("主进程执行结束")
- 子进程自己主动终止
import multiprocessing
import timedef work():for i in range(10):print("工作中...")time.sleep(0.2)if __name__ == "__main__":# 创建子进程sub_process = multiprocessing.Process(target=work)sub_process.start()time.sleep(1)sub_process.terminate() # 终止子进程,不建议使用,僵尸进程,不会清理资源print("主进程执行结束")
线程
线程的概念
进程是分配资源的基本 单位,一旦创建一个进程就会分配一定的资源;现成是CPU调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。
线程的创建步骤
# 1. 导入线程模块
import threading
# 2. 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
# 启动线程执行任务
线程对象.start()
参数详解
线程对象=threading.Thread([group[,target[,name[,args[,kwargs]]]]])
- group: 线程组。目前只能使用None
- target 执行的目标任务名
- args: 以元组的方式给执行任务传参,元组方式传参一定要和目标任务函数参数顺序保持一致
- kwargs: 以字典方式给执行任务传参,字典方式传参字典中的key一定要和参数的顺序保持一致
- name: 线程名,一般不用设置
线程不带参数任务
例:使用线程模拟一边写代码,一边听音乐功能
import threading
import timedef coding():for i in range(3):print("正在写代码...")time.sleep(0.2)def music():for i in range(3):print("正在听音乐...")time.sleep(0.2)if __name__ == "__main__":# 创建子线程sub_thread1 = threading.Thread(target=coding)sub_thread2 = threading.Thread(target=music)# 启动子线程sub_thread1.start()sub_thread2.start()
线程带参数的任务
例:使用线程模拟一边编写num行代码,一边听count收音乐的功能
import threading
import timedef coding(num,name):for i in range(num):print(f"{name}正在写第{i}行代码...")time.sleep(0.2)def music(count,name):for i in range(count):print(f"{name}正在听第{i}首音乐...")time.sleep(0.2)if __name__ == "__main__":# 创建子线程sub_thread1 = threading.Thread(target=coding,args=(5,"zhangsan"))sub_thread2 = threading.Thread(target=music,kwargs={"count":5,"name":"lisi"})# 启动子线程sub_thread1.start()sub_thread2.start()
线程的注意介绍
- 线程之间执行时无序的
- 主线程会等待所有的子线程执行结束后再结束
- 线程之间共享全局变量
- 多个线程操作全局变量数据时要注意线程安全问题
守护线程
设置守护线程有两种方式
- 创建线程时
线程对象 = threading.Thread(target=任务名,daemon=True)
- 调用线程对象方法
线程对象.setDaemon(True)
线程安全
互斥锁
对共享数据进行锁定,保证同一时刻只有一个线程去操作
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等锁使用完释放后,其他等待的线程再去抢这个锁
互斥锁的使用流程
# 1. 互斥锁的创建
lock= threading.Lock()
# 2. 上锁
lock.acquire()
# 3. 释放锁
lock.release()
例:定义两个函数,实现循环100万次,每循环一次给全局变量加1,创建两个子线程执行对应的两个函数,添加互斥锁后,查看计算后的记过
import threading
# 定义全局变量
g_num = 0
# 创建互斥锁
lock = threading.Lock()def sum_num1():# 声明全局变量global g_numlock.acquire() # 上锁for i in range(1000000):g_num += 1lock.release() # 解锁print("sum1:", g_num)def sum_num2():# 声明全局变量global g_numlock.acquire() # 上锁for i in range(1000000):g_num += 1lock.release() # 解锁print("sum2:", g_num)if __name__ == "__main__":# 创建两个子线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)# 启动线程first_thread.start()second_thread.start()