文章目录
1.多任务的执行方式
- 并发
- 并行
并发:
在一段时间内交替去执行任务。
例如:
对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。
并行:
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。
小结
使用多任务就能充分利用CPU资源,提高程序的执行效率,让你的程序具备处理多个任务的能力。
多任务执行方式有两种方式:并发和并行,这里并行才是多个任务真正意义一起执行。
2. 进程的使用
# 1. 导入multiprocessing包
import multiprocessing
import time
# 定义跳舞的任务函数
def dance():
for i in range(3):
print("跳舞。。。")
time.sleep(0.2)
# 定义唱歌的任务函数
def sing():
for i in range(3):
print("唱歌。。。")
time.sleep(0.2)
# 判断是否是主模块
if __name__ == '__main__':
#sing()
#dance()
'''
唱歌。。。
唱歌。。。
唱歌。。。
跳舞。。。
跳舞。。。
跳舞。。。
'''
# 2. 创建进程对象
# group : 进程组,目前只能使用None, 一般不要管它
# target: 指定执行的任务名
sub_process1 = multiprocessing.Process(target=dance)
sub_process2 = multiprocessing.Process(target=sing)
# 3. 启动进程执行对应的任务
sub_process1.start()
sub_process2.start()
'''
唱歌。。。
跳舞。。。
唱歌。。。
跳舞。。。
唱歌。。。
跳舞。。。
'''
3.获取当前进程的编号
import time
import multiprocessing
import os
def sing():
# 获取当前的进程对象,并打印
sing_process = multiprocessing.current_process()
print("sing_process: ",sing_process.name)
# 获取唱歌子进程的编号
os_getpid = os.getpid()
print(f"唱歌子进程的编号:{os_getpid}")
# 获取当前进程的父进程
parent_id = os.getppid()
print(f"唱歌进程的父进程为:{parent_id}")
# 唱歌逻辑实现
for i in range(3):
print("唱歌...")
time.sleep(0.5)
def dance():
# 获取当前的进程对象,并打印
dance_process = multiprocessing.current_process()
print("dance_process: ",dance_process.name)
# 获取跳舞子进程的编号
os_getpid = os.getpid()
print(f"跳舞子进程的编号:{os_getpid}")
# 获取当前进程的父进程
parent_id = os.getppid()
print(f"唱歌进程的父进程为:{parent_id}")
# 跳舞逻辑实现
for i in range(3):
print("跳舞...")
time.sleep(0.5)
if __name__ == '__main__':
# 获取当前进程(主进程)的编号
getpid = os.getpid()
print(f"主进程id为{getpid}")
# 不使用进程
# sing()
# dance()
'''
主进程id为9288
MainProcess
唱歌子进程的编号:9288
当前进程的父进程为:24552
唱歌...
唱歌...
唱歌...
MainProcess
跳舞子进程的编号:9288
当前进程的父进程为:24552
跳舞...
跳舞...
跳舞...
'''
process_sing = multiprocessing.Process(target=sing)
process_dance = multiprocessing.Process(target=dance)
process_sing.start()
process_dance.start()
'''
主进程id为2336
sing_process: Process-1
唱歌子进程的编号:13024
dance_process: Process-2
跳舞子进程的编号:16908
唱歌进程的父进程为:2336
唱歌...
唱歌进程的父进程为:2336
跳舞...
唱歌...
跳舞...
唱歌...
跳舞...
'''
4.进程带有参数的任务
# 带参数执行任务:有两种方式,一种是直接以元组的方式进行,一种是以字典的形式进行
import multiprocessing
def task(name,count):
print(name)
print(multiprocessing.current_process())
for i in range(count):
print("%d执行任务" % i)
if __name__ == '__main__':
# 创建子进程
# 元组如果只有一个元素,那么元素后面的逗号不能省略
# args: 表示以元组方式给执行任务传参数, 实际上是按照函数位置参数进行传参的。
# 在创建进程的时候往进程中添加进程参数
sub_process = multiprocessing.Process(target=task,args=("张三",3),name="sub_process")
sub_process.start()
'''
张三
<Process(sub_process, started)>
0执行任务
1执行任务
2执行任务
'''
# 使用字典
# sub_process = multiprocessing.Process(target=task, kwargs={"name":"张三","count":3})
# sub_process.start()
'''
张三
<Process(Process-1, started)>
0执行任务
1执行任务
2执行任务
'''
5.进程间不能共享全局变量
import time
import multiprocessing
# 测试
# result = type([])
# print(result) # <class 'list'>
# 定义全局变量
g_list =list() # =》 [] 表示一个空列表
# 向全局变量中添加数据
def add_data():
for i in range(1,3):
g_list.append(i)
print("全局变量添加数据:%d",i)
time.sleep(0.5)
print("数据添加完毕。。。",g_list)
# 读取全局变量的数据
def read_data():
print("只读取数据进程读取:",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()
# read_data_process.start()
'''
只读取数据进程读取: []
全局变量添加数据:%d 1
全局变量添加数据:%d 2
数据添加完毕。。。 [1, 2]
'''
add_data_process.start()
# 进程等待 join, 主进程会等待子进程(add_data_process)执行完成以后,再继续执行下面的代码
add_data_process.join()
read_data_process.start()
print("获取主进程里面的g_list",g_list)
'''
全局变量添加数据:%d 1
全局变量添加数据:%d 2
数据添加完毕。。。 [1, 2]
获取主进程里面的g_list []
只读取数据进程读取: []
'''
6.主进程会等待所有的子进程执行完成之后再退出
import time
import multiprocessing
def task():
for i in range(3):
print("子进程工作中。。。")
time.sleep(0.5)
if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
# 创建主线程的守护进程,即主进程销毁的时候,守护进程也会直接销毁
sub_process.daemon = True
sub_process.start()
# 主进程暂停0.6秒,如果不加这个主进程的休眠时间,子线程是执行不出结果的
time.sleep(0.6)
# 让子进程销毁
# sub_process.terminate()
print("主进程over")
# exit()
# 默认:主进程会等待所有的子进程执行完成以后主进程再退出
# 主进程不等待子进程的操作
# 1. 主进程在退出执行,先让子进程进行销毁 sub_process.terminate()
# 2. 设置守护主进程,主进程退出子进程直接销毁,不再执行子进程里面的任务
7.线程的使用
import time
import threading
def sing():
for i in range(3):
print("唱歌。。。")
time.sleep(0.5)
def dance():
for i in range(3):
print("跳舞...")
time.sleep(0.5)
if __name__ == '__main__':
sub_thread1 = threading.Thread(target=sing)
sub_thread2 = threading.Thread(target=dance,name="sub_thread2")
print(sub_thread1,sub_thread1.name)
print(sub_thread2,sub_thread2.name)
sub_thread1.start()
sub_thread2.start()
'''
<Thread(Thread-1, initial)> Thread-1
<Thread(sub_thread2, initial)> sub_thread2
唱歌。。。
跳舞...
跳舞...
唱歌。。。
跳舞...
唱歌。。。
'''
8.线程带有参数执行任务
import threading
def task(name,age):
print("测试")
task_thread = threading.current_thread()
print("task_thread:",task_thread)
print("姓名:%s,年龄:%d" % (name,age))
if __name__ == '__main__':
main_thread = threading.current_thread()
print("main_thread:",main_thread)
sub_thread = threading.Thread(target=task, kwargs={"name": "貂蝉", "age": 19})
sub_thread.start()
9.线程之间的执行是无序的
import threading
import time
def task():
time.sleep(0.2)
print(threading.current_thread().name)
if __name__ == '__main__':
my_list = []
for i in range(10):
sub_thread = threading.Thread(target=task)
my_list.append(sub_thread)
for value in my_list:
value.start()
'''
Thread-2Thread-3
Thread-1
Thread-6
Thread-7
Thread-8
Thread-4Thread-5
Thread-9
Thread-10
'''
10.主线程会等待所有的子线程执行结束再结束
import threading
import time
def task():
for i in range(10):
print("子线程。。。")
time.sleep(0.3)
if __name__ == '__main__':
# 创建子线程
# 1. daemon=True 表示守护主线程,主线程退出子线程销毁
# sub_thread = threading.Thread(target=task, daemon=True)
# 2. setDaemon(True)表示守护主线程
sub_thread = threading.Thread(target=task)
# sub_thread.setDaemon(True)
sub_thread.start()
# time.sleep(1)
print("主线程结束。。。")
# 总结:
# 默认情况下,主线程会等待所有的子线程执行完成以后主线程再退出
# 解决办法:
# 1. 守护主线程: 1.1 setDaemon(True), 1.2 daemon=True
11.线程之间共享全局变量
import threading
import time
# 定义全局变量列表
g_list = []
# 向全局变量里面添加数据
def add_data():
for i in range(4):
g_list.append(i)
print("add_data:", i)
time.sleep(0.2)
# 代码执行到此
print("数据添加完成:", g_list)
def read_data():
print("read_data:", g_list)
if __name__ == '__main__':
sub_thread1 = threading.Thread(target=add_data)
sub_thread2 = threading.Thread(target=read_data)
sub_thread1.start()
# time.sleep(1)
# 线程等待, 主线程等待添加数据线程执行完成以后,代码再继续往下执行
sub_thread2.start()
12.线程之间共享全局变量出现错误问题
import threading
# 定义全局变量
g_num = 0
# 循环1000000次,每循环一次给全局变量加1
def calc_num1():
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(1000000):
g_num += 1
print("calc_num1:", g_num)
# 循环1000000次,每循环一次给全局变量加1
def calc_num2():
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(1000000):
g_num += 1
print("calc_num2:", g_num)
if __name__ == '__main__':
# 创建第一个子线程
first_thread = threading.Thread(target=calc_num1)
# 创建第二个子线程
second_thread = threading.Thread(target=calc_num2)
# 启动线程执行任务
first_thread.start()
# 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
first_thread.join()
second_thread.start()
'''
calc_num2: 10000
calc_num1: 20000
'''
线程注意点总结:
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
13.互斥锁
互斥锁同样解决全局共享数据读取问题:
import threading
# 定义全局变量
g_num = 0
# 定义一把锁
lock = threading.Lock()
# 循环1000000次,每循环一次给全局变量加1
def calc_num1():
# 在执行这个函数的时候先上锁
lock.acquire()
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(1000000):
g_num += 1
print("calc_num1:", g_num)
# 函数执行完毕以后,释放锁对象
lock.release()
# 循环1000000次,每循环一次给全局变量加1
def calc_num2():
# 在执行这个函数之前上锁
lock.acquire()
# 声明此处加上global表示要修改全局变量的内存地址
global g_num
for i in range(1000000):
g_num += 1
print("calc_num2:", g_num)
# 函数执行完毕之后
lock.release()
if __name__ == '__main__':
# 创建第一个子线程
first_thread = threading.Thread(target=calc_num1)
# 创建第二个子线程
second_thread = threading.Thread(target=calc_num2)
# 启动线程执行任务
first_thread.start()
# 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
# first_thread.join()
second_thread.start()
# 结论: 互斥锁可以解决全局变量数据错误问题,互斥锁可以保证同一时刻只有一个线程取值
# 性能: 执行代码的效率会下降,能够保证数据的安全性。
'''
calc_num2: 10000
calc_num1: 20000
'''
14.死锁
import threading
# 创建互斥锁
lock = threading.Lock()
# 保证同一时刻只有一个线程根据下标去取值
def get_value(index):
# 取值之前先上锁
lock.acquire()
my_list = [1, 4, 8]
# 判断下标是否越界
if index >= len(my_list):
print("下标越界:", index)
# 下标越界不能取值,也需要把锁释放,保证后面的线程可以再次取值
lock.release()
return
# 根据下标获取值
result = my_list[index]
print(result)
# 取值完成需要释放锁
lock.release()
if __name__ == '__main__':
# 创建很多线程,同时执行某个任务
for i in range(5):
# 创建子线程
sub_thread = threading.Thread(target=get_value, args=(i,))
# 启动线程执行任务
sub_thread.start()
'''
1
4
8
下标越界: 3
下标越界: 4
'''
15.进程与线程的对比
1. 进程和线程的对比的三个方向
关系对比
区别对比
优缺点对比
1. 关系对比
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程。
对比
2. 区别对比
-
进程之间不共享全局变量
-
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
-
创建进程的资源开销要比创建线程的资源开销要大
-
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
-
线程不能够独立执行,必须依存在进程中
-
多进程开发比单进程多线程开发稳定性要强
3. 优缺点对比
进程优缺点:
优点:可以用多核
缺点:资源开销大
线程优缺点:
优点:资源开销小
缺点:不能使用多核
4. 小结
- 进程和线程都是完成多任务的一种方式
- 多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
- 多进程可以使用cpu的多核运行,多线程可以共享全局变量。
- 线程不能单独执行必须依附在进程里面