一、多任务的介绍
1.多任务的概念
多任务是指在同一时间内执行多个任务
例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。
2.多任务的执行方式
- 并发
- 并行
并发: 在一段时间内交替去执行任务。
例如:对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。
并行: 对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。
二、进程
1. 进程的介绍
在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式
2. 进程的概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
3.多进程的作用
说明:多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。
三、多进程完成多任务
1.进程创建的步骤
①. 导入进程包 import multiprocessing
②. 通过进程类创建进程对象 进程对象 = multiprocessing.Process()
③. 启动进程执行任务 进程对象.start()
2.通过进程类创建进程对象
进程对象 = multiprocessing.Process(target=任务名)
参数名 | 说明 |
---|---|
target | 执行的目标任务名,这里指的是函数名(方法名) |
name | 进程名,一般不用设置 |
group | 进程组,目前只能用None |
3.代码演示
import multiprocessing
import time
# 编写代码
def coding():
for i in range(3):
print('coding...')
time.sleep(0.2)
# 听音乐
def music():
for i in range(3):
print('music...')
time.sleep(0.2)
if __name__ == '__main__':
# coding()
# music()
coding_process = multiprocessing.Process(target=coding)
music_process = multiprocessing.Process(target=music)
coding_process.start()
music_process.start()
4.进程执行带有参数的任务
参数名 | 说明 |
---|---|
args | 以元组的方式给执行任务传参 |
kwargs | 以字典的方式给执行任务传参 |
5.代码示例
import multiprocessing
import time
# 编写代码
def coding(num, name):
for i in range(num):
print('coding...')
print(name)
time.sleep(0.2)
# 听音乐
def music(count):
for i in range(count):
print('music...')
time.sleep(0.2)
if __name__ == '__main__':
coding_process = multiprocessing.Process(target=coding, args=(3, 'hahaha'))
music_process = multiprocessing.Process(target=music, kwargs={'count': 2})
coding_process.start()
music_process.start()
注意:
- 元组方式传参 :元组方式传参一定要和参数的顺序保持一致
- 字典方式传参:字典方式传参字典中的key一定要和参数名保持一致
四、获取进程编号
1.进程编号的作用
当程序中进程的数量越来越多时 , 如果没有办法区分主进程和子进程还有不同的子进程 , 那么就无法进行有效的进程管理 , 为了方便管理实际上每个进程都是有自己编号的
2.获取进程编号的两种操作
- 获取当前进程编号
os.getpid()
或者multiprocessing.current_process().pid
- 获取当前父进程编号
os.getppid()
3.获取进程编号
代码演示:
import multiprocessing
import time
import os
# 编写代码
def coding():
print('coding>>>%d' % os.getpid())
# current_process = multiprocessing.current_process()
# print(current_process.pid)
print('coding父进程>>>%d' % os.getppid())
for i in range(3):
print('coding...')
time.sleep(0.2)
# 听音乐
def music():
print('music>>>%d' % os.getpid())
print('music父进程>>>%d' % os.getppid())
for i in range(3):
print('music...')
time.sleep(0.2)
if __name__ == '__main__':
print('主进程>>>%d' % os.getpid())
coding_process = multiprocessing.Process(target=coding)
music_process = multiprocessing.Process(target=music)
coding_process.start()
music_process.start()
运行结果:
五、进程间不共享全局变量
1.进程间不共享全局变量
实际上创建一个子进程就是把主进程的资源进行拷贝产生了一个新的进程,这里主进程和子进程是相互独立的
2.代码演示
import multiprocessing
import time
# 全局变量
my_list = []
# write函数
def write_data():
for i in range(3):
my_list.append(i)
print('add:', i)
print('write_data', my_list)
# read函数
def read_data():
print('read_data', my_list)
if __name__ == '__main__':
# wirte进程
write_process = multiprocessing.Process(target=write_data)
# read进程
read_process = multiprocessing.Process(target=read_data)
write_process.start()
write_process.join()
# 或者time.sleep(1)
read_process.start()
观看一下运行结果:
3.总结
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
六、主进程和子进程的结束顺序
1.主进程会等待所有的子进程执行结束再结束
假如我们现在创建一个子进程,这个子进程执行完大概需要2秒钟,现在让主进程执行0.5秒钟就退出程序,查看一下执行结果,示例代码如下:
import multiprocessing
import time
# 定义进程所需要执行的任务
def task():
for i in range(10):
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
task_process = multiprocessing.Process(target=task)
task_process.start()
# 主进程延时0.5秒钟
time.sleep(0.5)
print("over")
exit()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
执行结果:
任务执行中...
任务执行中...
任务执行中...
over
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
说明:
通过上面代码的执行结果,我们可以得知: 主进程会等待所有的子进程执行结束再结束。而不能立马结束
假如我们就让主进程执行0.5秒钟,子进程就销毁不再执行,那怎么办呢?
- 我们可以设置守护主进程 或者 在主进程退出之前 让子进程销毁
守护主进程: 守护主进程就是主进程退出子进程销毁不再执行
子进程销毁: 子进程执行结束
保证主进程正常退出的示例代码
import multiprocessing
import time
# 定义进程所需要执行的任务
def task():
for i in range(10):
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
task_process = multiprocessing.Process(target=task)
# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
# task_process.daemon = True
task_process.start()
time.sleep(0.5)
print("over")
# 让子进程销毁
task_process.terminate()
exit()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
# 如果想要主进程退出子进程销毁,可以设置守护主进程或者在主进程退出之前让子进程销毁
执行结果:
任务执行中...
任务执行中...
任务执行中...
over
2.小结
- 为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行。
- 设置守护主进程方式:
子进程对象.daemon = True
- 销毁子进程方式:
子进程对象.terminate()
七、线程
1.为什么使用多线程?
进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 .
线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 . 同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源 . 这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源 .
2.多线程的作用
八、多线程完成多任务
1.线程的创建步骤
- 导入线程模块
import threading
- 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
- 启动线程执行任务
线程对象.start()
2.通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
参数名 | 说明 |
---|---|
target | 执行的目标任务名,这里指的是函数名(方法名) |
name | 线程名,一般不设置 |
group | 线程组,目前只能用None |
3.代码示例
import time
import threading
def coding():
for i in range(3):
print("coding...")
time.sleep(0.2)
def music():
for i in range(3):
print("music...")
time.sleep(0.2)
if __name__ == '__main__':
coding_thread = threading.Thread(target=coding)
music_thread = threading.Thread(target=music)
coding_thread.start()
music_thread.start()
4.线程执行带有参数的任务
参数名 | 说明 |
---|---|
args | 以元组的方式给执行任务传参 |
kwargs | 以字典的方式给执行任务传参 |
5.代码示例
import time
import threading
def coding(num):
for i in range(num):
print("coding...")
time.sleep(0.2)
def music(count):
for i in range(count):
print("music...")
time.sleep(0.2)
if __name__ == '__main__':
coding_thread = threading.Thread(target=coding, args=(3,))
music_thread = threading.Thread(target=music, kwargs={'count': 2})
coding_thread.start()
music_thread.start()
注意:
- 元组方式传参 :元组方式传参一定要和参数的顺序保持一致
- 字典方式传参:字典方式传参字典中的key一定要和参数名保持一致
九、主线程和子线程的结束顺序
假如我们现在创建一个子线程,这个子线程执行完大概需要2.5秒钟,现在让主线程执行1秒钟就退出程序,查看一下执行结果,示例代码如下:
import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
sub_thread = threading.Thread(target=show_info)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")
执行结果:
test: 0
test: 1
over
test: 2
test: 3
test: 4
说明:
通过上面代码的执行结果,我们可以得知: 主线程会等待所有的子线程执行结束再结束
假如我们就让主线程执行1秒钟,子线程就销毁不再执行,那怎么办呢?
- 我们可以设置守护主线程
守护主线程:
- 守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
threading.Thread(target=show_info, daemon=True)
线程对象.setDaemon(True)
设置守护主线程的示例代码:
import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
# 创建子线程守护主线程
# daemon=True 守护主线程
# 守护主线程方式1
sub_thread = threading.Thread(target=show_info, daemon=True)
# 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
# 守护主线程方式2
# sub_thread.setDaemon(True)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")
十、线程间的执行顺序
1.线程之间执行是无序的
import threading
import time
def task():
time.sleep(1)
print("当前线程:", threading.current_thread().name)
if __name__ == '__main__':
for _ in range(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()
执行结果:
当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3
说明:
- 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
- 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
十一、线程间共享全局变量
1.线程之间共享全局变量
多个线程都是在同一个进程中 , 多个线程使用的资源都是同一个进程中的资源 , 因此多线程间是共享全局变量
2.代码示例
import threading
import time
# 全局变量
my_list = []
# 写入数据
def write_data():
for i in range(3):
print("add:", i)
my_list.append(i)
print("write:", my_list)
# 读取数据
def read_data():
print("read:", my_list)
if __name__ == '__main__':
write_thread = threading.Thread(target=write_data)
read_thread = threading.Thread(target=read_data)
write_thread.start()
write_thread.join()
read_thread.start()
执行结果:
十二、线程间资源竞争问题
1.线程之间共享全局变量数据出现错误问题
需求:
- 定义两个函数,实现循环100万次,每循环一次给全局变量加1
- 创建两个子线程执行对应的两个函数,查看计算后的结果
2.代码示例
import threading
# 定义全局变量
g_num = 0
# 循环一次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环一次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
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()
执行结果:
sum1: 1210949
sum2: 1496035
注意点: 多线程同时对全局变量操作数据发生了错误
3.分析
错误分析:
两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:
- 在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0
- 然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1
- 然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
- 这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1
4.全局变量数据错误的解决办法
线程同步: 保证同一时刻只能有一个线程去操作全局变量
同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机
线程同步的方式:
- 线程等待(join)
- 互斥锁
5.线程等待的代码示例
import threading
# 定义全局变量
g_num = 0
# 循环1000000次每次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环1000000次每次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
# 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行
first_thread.join()
# 启动线程
second_thread.start()
执行结果:
sum1: 1000000
sum2: 2000000
十三、互斥锁
1.1.互斥锁的概念
互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
注意:
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
2.互斥锁的使用
- 互斥锁的创建
mutex = threading.Lock()
- 上锁
mutex.acquire()
- 释放锁
mutex.release()
3.代码演示
import threading
# 定义全局变量
g_num = 0
# 创建全局互斥锁
lock = threading.Lock()
# 循环一次给全局变量加1
def sum_num1():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 释放锁
lock.release()
# 循环一次给全局变量加1
def sum_num2():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
# 释放锁
lock.release()
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
second_thread.start()
# 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待
# 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行
执行结果:
sum1: 1000000
sum2: 2000000
说明: 通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题
十四、死锁
死锁: 一直等待对方释放锁的情景就是死锁
死锁的结果: 会造成应用程序的停止响应,不能再处理其它任务了
死锁注意点:
- 使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁。
- 死锁一旦产生就会造成应用程序的停止响应,应用程序无法再继续往下执行了。
十五、进程和线程对比
1.关系对比
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程
2.区别对比
- 进程之间不共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依存在进程中
3.优缺点对比
十六、Queue的使用
1.Queue是什么?
Queue是一个消息列队,具有先进先出的特点,它是可以实现多进程之间的数据传递
2.Queue的创建和实例方法
创建: queue = multiprocessing.Queue(3) # 创建消息队列, 3:表示队列中最大消息个数
实例方法:
方法名 | 说明 |
---|---|
put(item) | 将item消息写入队列 |
get() | 获取队列中的数据 |
qsize() | 返回当前队列包含的消息个数 |
3.代码示例
import multiprocessing
def get_data(size):
for i in range(size):
print(queue.get())
if __name__ == '__main__':
queue = multiprocessing.Queue(3)
# 队列可以放入任意数据类型
queue.put(1)
queue.put('hello')
queue.put([3, 5])
size = queue.qsize()
get_data(size)
注意: Queue是进程之间数据传递的一个工具, 队列里面可以放入任意类型数据。
十七、Queue演练
需求:
使用Queue完成进程之间通信,一个进程负责往Queue对象中添加数据,另一个进程负责从Queue对象中读取数据。
代码演示:
import multiprocessing
def write_data(queue):
for i in range(10):
queue.put(i)
def read_data(queue):
for i in range(10):
print("获取的数据:", queue.get())
if __name__ == '__main__':
queue = multiprocessing.Queue(10)
write_process = multiprocessing.Process(target=write_data, args=(queue,))
read_process = multiprocessing.Process(target=read_data, args=(queue,))
write_process.start()
write_process.join()
read_process.start()
上一篇:Linux高级命令
下一篇:Python网络编程