一、多任务
1. 多任务的概念
多任务是指在同一时间内执行多个任务,例如:现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。
2. 多任务的执行方式
并发:在一段时间内交替去执行任务。
并行:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。
二、进程
1. 进程的介绍
在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。
2. 进程的概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
注意:一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。
三、进程的使用
1. 代码实现
"""
Process([group[,target[.name[,args[,kwargs]]]]])
group:指定进程组,目前只能用None
target:执行的目标任务名
name:进程名
args:以元祖方式给执行任务传参
kwargs:以字典方式给执行任务传参
Process创建实例对象的常用方法:
start():启动子进程实例(创建子进程)
join():等待子进程执行结束
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
"""
import multiprocessing
import time
def process_1():
for i in range(5):
print(f"开发功能{i}")
time.sleep(0.5)
def process_2():
for i in range(5):
print(f"测试功能{i}")
time.sleep(0.5)
if __name__ == '__main__':
sub_process1 = multiprocessing.Process(target=process_1)
sub_process2 = multiprocessing.Process(target=process_2)
sub_process1.start()
sub_process2.start()
2. 获取进程编号
目的:获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由哪个主进程创建出来的。
import multiprocessing
import time
import os
def process_1():
# 获取当前代码的进程对象
p1 = multiprocessing.current_process()
print("p1:", p1)
# 获取当前进程(子进程)编号
p1_pid = os.getpid()
# 获取当前进程的父进程编号
p1_ppid = os.getppid()
print("p1_pid:", p1_pid)
print("p1_ppid:", p1_ppid)
for i in range(5):
print(f"开发功能{i}")
time.sleep(0.5)
def process_2():
# 获取当前代码的进程对象
p2 = multiprocessing.current_process()
print("p1:", p2)
# 获取当前进程(子进程)编号
p2_pid = os.getpid()
# 获取当前进程的父进程编号
p2_ppid = os.getppid()
print("p2_pid:", p2_pid)
print("p2_ppid:", p2_ppid)
for i in range(5):
print(f"测试功能{i}")
time.sleep(0.5)
if __name__ == '__main__':
sub_process1 = multiprocessing.Process(target=process_1)
sub_process2 = multiprocessing.Process(target=process_2)
sub_process1.start()
sub_process2.start()
3. 进程执行带有参数的任务
import multiprocessing
def func1(act, count):
act = act
for i in range(count):
print(f"{act}产品{i}")
if __name__ == '__main__':
# 创建子进程
# 元组如果只有一个元素,那么元素后面的逗号不能省略
# args:表示以元组的方式给执行任务传参,实际上是按照函数位置参数进行传参的
# sub_porcess = multiprocessing.Process(target=func1, args=("研发", 3))
# sub_porcess.start()
# kwargs:表示以字典方式给执行任务传参,实际上是按照函数关键字传参的
sub_process = multiprocessing.Process(target=func1, kwargs={"act":"研发", "count":3})
sub_process.start()
4. 进程之间不共享全局变量
import multiprocessing
import time
g_list = list()
def add_data():
for i in range(3):
g_list.append(i)
time.sleep(0.3)
print("add_data:", g_list)
def read_data():
print("read_data:", g_list)
if __name__ == '__main__':
add_data_process = multiprocessing.Process(target=add_data)
read_data_process = multiprocessing.Process(target=read_data)
add_data_process.start()
# 进程等待,主进程等待子进程(add_data_process)执行完成以后,再执行下面的代码
add_data_process.join()
read_data_process.start()
print("全局变量g_list:", g_list)
5. 主进程会等待所有的子进程完成以后再退出
import multiprocessing
import time
def task():
for i in range(3):
print("子进程正在运行...")
time.sleep(0.5)
if __name__ == '__main__':
sub_process = multiprocessing.Process(target=task)
# 1. 设置守护主进程,主进程退出子进程就销毁,停止程序运行
# sub_process.daemon = True
sub_process.start()
# 主进程延时1秒
time.sleep(1)
# 2. 在此处销毁子进程
sub_process.terminate()
print("主进程挂了!")
四、线程
1. 线程的介绍
在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。
2. 线程的概念
线程是进程中执行代码的一个分支,每个执行分支(线程)想要工作执行代码需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。
3. 线程的作用
多线程可以完成多任务
程序启动默认会有一个主线程,程序自己创建的线程可以称为子线程,多线程可以完成多任务。
线程是python程序中实现多任务的另外一种方式,线程的执行需要cpu调度来完成。
"""
Thread([group[, target[, name[, args[, kwargs]]]]])
group:线程组,目前只能使用None
target:执行的目标任务名
args:以元组的方式给执行任务传参
kwargs:以字典方式给执行任务传参
name:线程名,一般不设置
"""
import threading
import time
def thread_1():
for i in range(3):
print("研发功能")
time.sleep(0.1)
def thread_2():
for i in range(3):
print("测试功能")
time.sleep(0.1)
if __name__ == '__main__':
sub_thread1 = threading.Thread(target=thread_1)
sub_thread2 = threading.Thread(target=thread_2)
sub_thread1.start()
sub_thread2.start()
4. 线程执行带有参数的任务
import threading
import time
def thread_1(act, t):
for i in range(t):
print(f"{act}功能{i}")
time.sleep(0.1)
if __name__ == '__main__':
# 获取当前执行代码的线程
main_thread = threading.current_thread()
print("main_thread:", main_thread)
# sub_thread = threading.Thread(target=thread_1, args=("研发", 3))
sub_thread = threading.Thread(target=thread_1, kwargs={"act":"研发", "t":3})
# 打印线程名
print(sub_thread.name)
sub_thread.start()
5. 线程之间执行是无序的
import threading
import time
def task():
time.sleep(1)
print(threading.current_thread().name)
if __name__ == '__main__':
# for i in range(10):
# sub_thread = threading.Thread(target=task)
# sub_thread.start()
t_list = []
for i in range(10):
sub_thread = threading.Thread(target=task)
t_list.append(sub_thread)
for t in t_list:
t.start()
线程之间执行是无序的,它是由cpu调度决定的,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
6. 主线程会等待所有的子线程执行完成以后再结束
import threading
import time
def task():
for i in range(10):
print("执行子线程")
time.sleep(0.3)
if __name__ == '__main__':
# 守护主线程有以下两种方法,主线程退出子线程销毁
sub_thread = threading.Thread(target=task, daemon=True)
# sub_thread.setDaemon(True)
sub_thread.start()
time.sleep(1)
print("主线程结束")
# 默认情况下,主线程会等待所有的子线程执行完成以后再退出
7. 线程之间共享全局变量
import threading
import time
# 定义全局变量
g_list = []
def add_data():
for i in range(3):
g_list.append(i)
time.sleep(0.1)
print("add_data:", g_list)
def read_data():
print("read_data:", g_list)
if __name__ == '__main__':
add_data_thread = threading.Thread(target=add_data)
read_data_thread = threading.Thread(target=read_data)
add_data_thread.start()
add_data_thread.join()
read_data_thread.start()
8. 线程之间共享全局变量数据错误问题
import threading
# 定义全局变量
g_num = 0
def add_num1():
global g_num
for i in range(100000):
g_num += 1
print("add_num1:", g_num)
def add_num2():
global g_num
for i in range(100000):
g_num += 1
print("add_num2:", g_num)
if __name__ == '__main__':
add_num1_thread = threading.Thread(target=add_num1)
add_num2_thread = threading.Thread(target=add_num2)
add_num1_thread.start()
add_num2_thread.start()
解决办法1:线程同步
import threading
# 定义全局变量
g_num = 0
def add_num1():
global g_num
for i in range(100000):
g_num += 1
print("add_num1:", g_num)
def add_num2():
global g_num
for i in range(100000):
g_num += 1
print("add_num2:", g_num)
if __name__ == '__main__':
add_num1_thread = threading.Thread(target=add_num1)
add_num2_thread = threading.Thread(target=add_num2)
add_num1_thread.start()
# 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
add_num1_thread.join()
add_num2_thread.start()
解决办法2:互斥锁
"""
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。threading 模块中定义了 Lock 变量,
这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放
后,其它等待的线程再去抢这个锁。acquire 和 release 方法之间的代码同一时刻只能有一个线程去操作。
如果在调用 acquire 方法的时候,其他线程已经使用了这个互斥锁,那么此时 acquire 方法会堵塞,直到
这个互斥锁释放后才能再次上锁。
"""
import threading
# 定义全局变量
g_num = 0
# 创建互斥锁,本质上是一个函数
lock = threading.Lock()
def add_num1():
# 操作共享数据之前上锁
lock.acquire()
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(100000):
g_num += 1
print("add_num1:", g_num)
# 共享数据操作完成以后,需要释放锁
lock.release()
def add_num2():
# 操作共享数据之前上锁
lock.acquire()
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(100000):
g_num += 1
print("add_num2:", g_num)
# 共享数据操作完成以后,需要释放锁
lock.release()
if __name__ == '__main__':
add_num1_thread = threading.Thread(target=add_num1)
add_num2_thread = threading.Thread(target=add_num2)
add_num1_thread.start()
add_num2_thread.start()
# 互斥锁可以解决全局变量数据错误问题,互斥锁可以保证同一时刻只有一个线程取值
# 执行代码效率会下降,但能够保证数据的安全性
"""
死锁:一直等待对方释放锁的情景就是死锁。死锁会造成应用程序停止响应,不再处理其它任务。
"""
import threading
lock = threading.Lock()
def get_num(num):
lock.acquire()
l = [1, 3, 4]
for i in range(num):
if i >= len(l):
print(f"索引{i}越界!")
# 进入该逻辑return之前要释放锁
lock.release()
return
print({"index": i, "value": l[i]})
lock.release()
if __name__ == '__main__':
for j in range(10):
sub_thread = threading.Thread(target=get_num, args=(j,))
sub_thread.start()
9. 进程和线程的对比
(1)线程是依附在进程里面的,没有进程就要没有线程;
(2)一个进程默认提供一条线程,进程可以创建多个线程;
(3)进程之间不共享全局变量;
(4)线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或者线程同步;
(5)创建进程的资源开销要比创建线程的资源开销大;
(6)进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位;
(7)线程不能独立运行,必须依存在进程中;
(8)多进程开发比单进程多线程开发稳定性强;
(9)进程可以用多核,但资源开销大;线程资源开销小,但不能用多核;