目录
并发并行:
python是解释器逐行解释的,默认是不能实现代码中的方法或函数同时运行的,
此时开启多进程或者多线程即可实现!
- 并发
- 并行
并发: 单核多线程交替执行
在一段时间内交替去执行任务。
例如:
对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。
并行: 多核实现多软件一起执行
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。
主进程子进程 执行此代码的进程 子进程 主进程创建的进程,主子线程也是一样!
使用场景
多进程适合在cpu 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)
多线程适合在 IO密集型操作(读写数据操作比较多的,比如爬虫)
进程:
Process([group [, target [, name [, args [, kwargs]]]]])
- group:指定进程组,目前只能使用None
- target:执行的目标任务名
- name:进程名字
- args:以元组方式给执行任务传参
- kwargs:以字典方式给执行任务传参
Process创建的实例对象的常用方法:
- start():启动子进程实例(创建子进程)
- join():等待子进程执行结束
- is_alive() 是否存活
- terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
子进程.name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
multiprocessing.current_process().name 主进程名字
多进程完成多任务的代码
import multiprocessing
import time
# 跳舞任务
def dance():
for i in range(5):
print("跳舞中...")
time.sleep(0.2)
# 唱歌任务
def sing():
for i in range(5):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建跳舞的子进程
# group: 表示进程组,目前只能使用None
# target: 表示执行的目标任务名(函数名、方法名)
# name: 进程名称, 默认是Process-1, .....
dance_process = multiprocessing.Process(target=dance, name="myprocess1")
sing_process = multiprocessing.Process(target=sing)
# 启动子进程执行对应的任务
dance_process.start()
sing_process.start()
'''
唱歌中...
跳舞中...
唱歌中...跳舞中...
跳舞中...唱歌中...
跳舞中...唱歌中...
唱歌中...跳舞中...
'''
获取进程编号
- 获取当前进程编号
- os.getpid()
- 获取当前父进程编号
- os.getppid()
- 获取进程编号可以查看父子进程的关系
import multiprocessing
import os
import time
def sing():
# 获取当前进程的编号
print('sing当前进程的编号',os.getpid())
# 获取当前进程
print('sing当前进程',multiprocessing.current_process().name)
for i in range(5):
print('唱歌中...')
time.sleep(.5)
def dance():
# 获取当前进程的编号
print('dance当前进程的编号', os.getpid())
# 获取当前进程
print('dance当前进程', multiprocessing.current_process().name)
for i in range(5):
print('跳舞中...')
time.sleep(.5)
if __name__=='__main__':
#获取当前进程编号
print('主进程编号',os.getpid())
# 获取当前进程
print('当前进程',multiprocessing.current_process().name)
sing_pro=multiprocessing.Process(target=sing)
dance_pro=multiprocessing.Process(target=dance)
dance_pro.start()
sing_pro.start()
'''
主进程编号 33156
当前进程 MainProcess
sing当前进程的编号 35484
sing当前进程 Process-1
唱歌中...
dance当前进程的编号 33880
dance当前进程 Process-2
跳舞中...
唱歌中...
跳舞中...
跳舞中...唱歌中...
唱歌中...跳舞中...
唱歌中...跳舞中...
'''
进程执行带有参数的任务
args 元组
import multiprocessing
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子进程
# args: 以元组的方式给任务传入参数
sub_process = multiprocessing.Process(target=task, args=(5,))
sub_process.start()
执行结果:
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成
kwargs 字典
import multiprocessing
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子进程
# kwargs: 表示以字典方式传入参数
sub_process = multiprocessing.Process(target=task, kwargs={"count": 3})
sub_process.start()
执行结果:
任务执行中..
任务执行中..
任务执行中..
任务执行完成
注意:字典中的key一定要和参数名保持一致
进程之间不共享全局变量
操作进程间的全局列表,a子进程增加列表数据,主进程和其他子进程无法获取a添加的数据
import multiprocessing
glist=list()
# args参数是元组
def add_data():
for i in range(5):
glist.append(i)
else:
print('增加任务执行完毕',glist)
def read_data():
print('read',glist)
if __name__=='__main__':
add_pro=multiprocessing.Process(target=add_data)
read_pro=multiprocessing.Process(target=read_data)
add_pro.start()
add_pro.join()
read_pro.start()
print('main',glist)
'''
增加任务执行完毕 [0, 1, 2, 3, 4]
main []
read []
'''
主进程会等待所有的子进程执行结束再结束
意思就是主进程结束了 子进程若没执行完毕依然会执行
import multiprocessing
import time
# 定义进程所需要执行的任务
def task():
for i in range(10):
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
sub_process.start()
# 主进程延时0.5秒钟
time.sleep(0.5)
print("over")
exit()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
执行结果:
任务执行中...
任务执行中...
任务执行中...
over
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
进程是同时执行的,此时子进程0.2秒执行一次 主进程0.5秒后退出,此时就出现以上顺序结果!
主进程执行完,子进程也跟着执行完的办法:
1.让子进程去守护主进程,子进程跟随主进程的生命周期子进程对象.daemon = True
2.或者再主进程执行完毕前让子进程终止执行子进程对象.terminate()
import multiprocessing
import time
# 定义进程所需要执行的任务
def task():
for i in range(10):
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
# sub_process.daemon = True
sub_process.start()
time.sleep(0.5)
print("over")
# 让子进程销毁
sub_process.terminate()
exit()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
# 如果想要主进程退出子进程销毁,可以设置守护主进程或者在主进程退出之前让子进程销毁
执行结果:
任务执行中...
任务执行中...
任务执行中...
over
线程
线程是实现多任务的另外一种方式
线程使用方式和进程一致
import threading
import time
def sing(count):
for i in range(count):
print("正在唱歌...%d" % i)
time.sleep(1)
def dance(count):
for i in range(count):
print("正在跳舞...%d" % i)
time.sleep(1)
if __name__=='__main__':
sing=threading.Thread(target=sing,args=(5,))
dance=threading.Thread(target=dance,kwargs={'count':5})
sing.start()
dance.start()
结果是交替执行的
线程的注意点介绍
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
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调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
- 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
守护主线程
- 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")
执行结果:
test: 0
test: 1
over
线程间的全局数据共享
import threading
import time
# 定义全局变量
my_list = list()
# 写入数据任务
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.1)
print("write_data:", my_list)
# 读取数据任务
def read_data():
print("read_data:", my_list)
if __name__ == '__main__':
# 创建写入数据的线程
write_thread = threading.Thread(target=write_data)
# 创建读取数据的线程
read_thread = threading.Thread(target=read_data)
write_thread.start()
# 延时
# time.sleep(1)
# 主线程等待写入线程执行完成以后代码在继续往下执行
write_thread.join()
print("开始读取数据啦")
read_thread.start()
执行结果:
write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]
多线程同时对全局变量进行操作出现计算问题
import threading
num=0
def sum_num1():
for i in range(1000000):
global num
num+=1
print('sum1',num)
def sum_num2():
for i in range(1000000):
global num
num+=1
print('sum2',num)
if __name__ == '__main__':
one=threading.Thread(target=sum_num1)
two=threading.Thread(target=sum_num2)
one.start()
two.start()
'''
sum1 1503155
sum2 2000000
'''
错误分析:
两个线程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
总结 其实线程交替完成执行的,一个再执行其余的就是休眠状态,
解决方案就是线程异步执行变为同步,
- 线程等待(join)
- 互斥锁
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
互斥锁:
全局互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
就是有一把线程锁 a b 2个子线程执行的时候 执行到a 则给a先上锁 执行完毕再释放锁,此时轮到b执行方式也是一样
lock.acquire() 上锁
lock.release() 释放锁
import threading
num=0
# 全局锁
lock=threading.Lock()
def sum_num1():
lock.acquire()
for i in range(1000000):
global num
num+=1
print('sum1',num)
lock.release()
def sum_num2():
lock.acquire()
for i in range(1000000):
global num
num+=1
print('sum2',num)
lock.release()
if __name__ == '__main__':
one=threading.Thread(target=sum_num1)
two=threading.Thread(target=sum_num2)
one.start()
# one.join()
two.start()
'''
sum1 1000000
sum2 2000000
'''
死锁;
就是互斥锁使用的时候若出现return break if else 等关键字执行的时候难免出现执行不到方法的最后此时,只是再函数的最后释放锁,此时return出去了,此释放锁没有执行到,造成死锁!
一处上锁 多出释放锁!
import threading
import time
# 创建互斥锁
lock = threading.Lock()
# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):
# 上锁
lock.acquire()
print(threading.current_thread())
my_list = [3,6,8,1]
# 判断下标释放越界
if index >= len(my_list):
print("下标越界:", index)
# 这个位置也要释放
lock.release()
return
value = my_list[index]
print(value)
time.sleep(0.2)
# 释放锁
lock.release()
if __name__ == '__main__':
# 模拟大量线程去执行取值操作
for i in range(30):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()
进程线程对比:
2. 区别对比
-
进程之间不共享全局变量
-
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
-
创建进程的资源开销要比创建线程的资源开销要大
-
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
-
线程不能够独立执行,必须依存在进程中
-
多进程开发比单进程多线程开发稳定性要强
3. 优缺点对比
- 进程优缺点:
- 优点:可以用多核
- 缺点:资源开销大
- 线程优缺点:
- 优点:资源开销小
- 缺点:不能使用多核
4. 小结
- 进程和线程都是完成多任务的一种方式
- 多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
- 多进程可以使用cpu的多核运行,多线程可以共享全局变量。
- 线程不能单独执行必须依附在进程里面