Python 多进程和多线程 的使用
Posted IT_Holmes
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 多进程和多线程 的使用相关的知识,希望对你有一定的参考价值。
文章目录
1. Python 多进程 创建使用
程序本身开始就会是一个主进程,主进程里默认就会有一个主线程。主进程中可以创建多个子进程。
多进程的创建使用三个步骤:
import time
# 1.导包
import multiprocessing
def dance():
for i in range(5):
# 故意给他推迟1秒
time.sleep(1)
print("跳舞",i)
def sing():
for i in range(5):
time.sleep(1)
print("唱歌",i)
if __name__ == '__main__':
# 整个py文件就是一个进程(主进程),可以在它的下面创建多个子进程。
# 2.创建子进程
# Process的参数:
# target:指定执行的任务名(函数名)
my_dance = multiprocessing.Process(target=dance)
my_sing = multiprocessing.Process(target=sing)
# 3. 开启子进程(如果不开启是不会执行子进程的)
my_sing.start()
my_dance.start()
很简单的三步,导包(import),创建子进程(Process),开启子进程(start)。这就是一个简单的多进程案例。
2. 进程的编号 id
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。
获取进程编码的两种操作:
- os.getpid() 获取当前进程编号。
- os.getppid() 获取当前父进程编号。
import time
import multiprocessing
import os
def dance():
# 获取子进程id,因为当前函数就是子进程
print('dance子进程id:',os.getpid())
# 获取dance子进程的父进程
print('dance的父进程:',os.getppid())
for i in range(5):
time.sleep(1)
print("跳舞",i)
def sing():
# 获取子进程id
print('sing子进程id:',os.getpid())
# 获取sing子进程的父进程
print('sing的父进程:',os.getppid())
for i in range(5):
time.sleep(1)
print("唱歌",i)
if __name__ == '__main__':
# 获取当前主进程的id编号
print('主进程id:',os.getpid())
my_dance = multiprocessing.Process(target=dance)
my_sing = multiprocessing.Process(target=sing)
my_sing.start()
my_dance.start()
3. Process name 参数(进程名)
我们通过name参数定义进程名。
这里只需要记住怎么获取进程名即可:print(multiprocessing.current_process())
import multiprocessing
def dance():
# 获取进程名
print(multiprocessing.current_process())
for i in range(5):
print("跳舞")
if __name__ == '__main__':
my_dance = multiprocessing.Process(target=dance,name='老王')
my_dance.start()
4. 多进程的参数 args 和 kwargs
args:元组(单个元素的元组有 , 号)
kwargs:字典的形式(key值要和函数中的形参完全一致)
import multiprocessing
def dance(a,b):
for i in range(5):
print("跳舞",i)
a+=b
print(a)
def sing(张三,李四):
for i in range(5):
print(张三,李四)
if __name__ == '__main__':
# 带有参数的函数
# args:元组(单个元素的元组有 , 号)
my_dance = multiprocessing.Process(target=dance,args=(5,6,))
# kwargs:字典的形式(key值要和函数中的形参完全一致)
# 注意这里字典的key值必须是字符串类型(不可变类型!!)
my_sing = multiprocessing.Process(target=sing,kwargs={"张三":1,"李四":2,})
my_dance.start()
my_sing.start()
5. 进程之间不共享全局变量
进程是分配资源的最小单位 ==》每一个进程(主进程,子进程)都会有自己的独立空间。
创建子进程会对主进程进行拷贝。
因此总体上,进程之间是不共享全局变量的!
import multiprocessing
import time
# 定义一个全局变量列表
g_num = []
def my_write():
global g_num
# 向全局变量g_num里写入数据
for i in range(5):
# 向g_num添加数据
g_num.append(i)
print("my_write:",g_num)
def my_read():
global g_num
print("my_read:",g_num)
if __name__ == '__main__':
my_wri = multiprocessing.Process(target=my_write)
my_rea = multiprocessing.Process(target=my_read)
my_wri.start()
# 先让上面写入全局完成后在读取
time.sleep(1)
my_rea .start()
# my_write: [0, 1, 2, 3, 4]
# my_read: []
# 发现进程之间是不共享全局变量的!!!
6. 守护进程
在系统默认主进程和子进程中,如果主进程先执行完毕,那么它会等待子进程结束之后,再结束!!!
现在我们想要主进程退出子进程销毁,不让主进程再等待子进程去执行。
- 第一种方式,设置守护主进程方式:子进程对象.daemon = True
- 第二种方式,销毁子进程方式:子进程对象.terminate()
import multiprocessing
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func")
if __name__ == '__main__':
# 默认:主进程会等待子进程结束之后,再结束!!!
# 程序一旦执行,就默认创建主进程。
my_func = multiprocessing.Process(target=func)
# 方式1:设置守护主进程(进程),注意:这个设置必须start()方法之前设置。
# my_func.daemon = True
my_func.start()
time.sleep(0.5)
# 方式2:手动设置,terminate()方法结束子进程。
my_func.terminate()
print("主进程结束")
exit() # 参数0:无错误退出,参数1:有错误退出
# exit()退出程序,主进程结束,子进程也结束
7. 多线程
7.1 线程 介绍
在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。
一个程序启动,就会有一个主进程,这个主进程中就包含有一个主线程。
多线程执行效果图:
7.2 多线程 实现方式
线程是使用资源的最小单位 ==> 依附于进程,也就是先有了进程才有了线程,它俩是从属关系。
多线程的使用:
- 导包: import threading
- 创建子线程:threading.Thread(一系列参数)
- 开启子线程:xxx.start()
# 1. 导包
import threading
import time
def dance():
for i in range(5):
print("跳舞",i)
time.sleep(1)
def sing():
for i in range(5):
print("唱歌",i)
time.sleep(1)
if __name__ == '__main__':
# 2.创建子线程
my_dance = threading.Thread(target=dance)
my_sing = threading.Thread(target=sing)
# 3. 开启子线程
my_sing.start()
my_dance.start()
多线程 threading.Thread()的参数:
和多进程的参数一样,args和kwargs,一个元组,一个字典。
注意:字典的key值要与函数的形参完全重名才能赋予。
import threading
def dance(count):
for i in range(count):
print("跳舞",i)
if __name__ == '__main__':
# args参数元组:
# my_dance = threading.Thread(target=dance,args=(5,))
# kwargs参数元组:
my_dance = threading.Thread(target=dance, kwargs={"count":5})
my_dance.start()
上面代码中,一个进程两个线程(1个主进程,1个主线程,1个子线程),一定区分好进程和线程个数。
线程注意点:
- 线程之间执行是无序的。
- 主线程会等待所有的子线程执行结束再结束。
- 线程之间共享全局变量。
- 线程之间共享全局变量数据出现错误问题。
8. 线程的无序执行
所谓的无序执行,就是当线程进入阻塞状态时,都想使用cpu是无序的。
import threading
import time
def func():
time.sleep(2)
print(threading.current_thread())
# <Thread(Thread-3, started 7640)> 第一次执行的是thread3
# <Thread(Thread-2, started 6076)> 第二次执行的是thread2
# <Thread(Thread-1, started 6700)> 总之是无序运行的 ...
# <Thread(Thread-5, started 2756)>
# <Thread(Thread-4, started 588)>
if __name__ == '__main__':
# 这里的for循环仅仅是创建了5个子线程
for i in range(5):
# 循环my_func是被重新赋值的
my_func = threading.Thread(target=func)
my_func.start()
9. 守护线程
同样,主线程会等待所有子线程执行结束后再结束。
import threading
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func")
if __name__ == '__main__':
my_func = threading.Thread(target=func)
my_func.start()
time.sleep(0.5)
print("主线程已经执行结束正在等待子线程结束。")
守护主线程:
守护主进程就是主线程退出子线程要被销毁不再执行。
- 方式一:threading.Thread(target=show_info,daemon=True)
- 方式二:线程对象.setDaemon(True)
import threading
import time
def func():
for i in range(5):
time.sleep(0.2)
print("func")
if __name__ == '__main__':
# 方式一:Thread(daemon=True)
# my_func = threading.Thread(target=func,daemon=True)
my_func = threading.Thread(target=func)
# 方式二:
my_func.setDaemon(True)
my_func.start()
time.sleep(0.5)
print("主线程已经执行结束正在等待子线程结束。")
# func
# func
# 主线程已经执行结束正在等待子线程结束。
10. 线程之间 共享全局变量
由于线程是依附于进程的,一个进程中的所有的线程都是使用的同一片内存空间。
也就是说一个进程中,线程是共享该进程的全局变量。
import threading
import time
g_num = []
def my_write():
global g_num
for i in range(5):
g_num.append(i)
print("write:",g_num)
def my_read():
global g_num
print("read:",g_num)
if __name__ == '__main__':
sub_write = threading.Thread(target=my_write)
sub_read = threading.Thread(target=my_read)
sub_write.start()
time.sleep(1)
sub_read.start()
# 线程之间共享全局变量
# write: [0, 1, 2, 3, 4]
# read: [0, 1, 2, 3, 4]
11. 线程共享全局变量 出现的问题
如果两个线程同时操作全局变量,可能会出现非常严重的错误如下:
import threading
g_num = 0
def sum_num1():
global g_num
for i in range(1000000):
g_num+=1
print("sum_num1:",g_num)
def sum_num2():
global g_num
for i in range(1000000):
g_num+=1
print("sum_num2:",g_num)
if __name__ == '__main__':
sub_num1 = threading.Thread(target=sum_num1)
sub_num2 = threading.Thread(target=sum_num2)
sub_num1.start()
sub_num2.start()
# sum_num1: 1201969
# sum_num2: 1394111
# 期望的结果是10000000和20000000,但实际却不是!
为什么出现这种问题??
其实本质上,就是竞争cpu资源(由于CPU调度机制造成的)的事情,其中一方使用,另一方可能就会被搁置。
为了解决上面的问题就是用了一下内容:
- 使用线程同步,xxx.join()来解决。
- 也可以使用互斥锁解决。
12. 线程同步
线程同步:一个任务执行完成以后代码再继续执行,让其执行第二个线程。
使用join()可以达到线程同步的效果。
import threading
g_num = 0
def sum_num1():
global g_num
for i in range(1000000):
g_num+=1
print("sum_num1:",g_num)
def sum_num2():
global g_num
for i in range(1000000):
g_num+=1
print("sum_num2:",g_num)
if __name__ == '__main__':
sub_num1 = threading.Thread(target=sum_num1)
sub_num2 = threading.Thread(target=sum_num2)
sub_num1.start()
# 线程同步:一个任务执行完成以后代码再继续执行,让其执行第二个线程。
# sub_num1.join()作用:让主线程等待sub_num1执行完毕后,再往下执行。
sub_num1.join()
sub_num2.start()
# sum_num1: 1000000
# sum_num2: 2000000
13. 互斥锁
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁。
互斥锁的使用步骤:(三个步骤)
import threading
g_num = 0
# 1. 创建互斥锁(我们要操作下面的两个函数,因此要定义全局锁)
mutex = threading.Lock() # mutex就是一把互斥锁
def sum_num1():
# 2. 上锁
mutex.acquire()
global g_num
for i in range(1000000):
g_num+=1
print("sum_num1:",g_num)
# 3. 解锁(如果不解锁,就会产生死锁问题)
mutex.release()
def sum_num2():
# 2. 上锁(如果同一把锁,必须先解锁才可以上锁)
mutex.acquire()
# 两个函数执行时,一个占用资源被调用上锁,另一个函数无法上锁,cpu不会给它使用的资源,只能阻塞!!
global g_num
for i in range(1000000):
g_num+=1
print("sum_num2:",g_num)
# 3. 解锁(如果不解锁,就会产生死锁问题)
mutex.release()
if __name__ == '__main__':
sub_num1 = threading.Thread(target=sum_num1)
sub_num2 = threading.Thread(target=sum_num2)
sub_num1.start()
sub_num2.start()
# sum_num1: 1000000
# sum_num2: 2000000
注意事项:
14. 解释一下互斥锁产生的 死锁问题
死锁问题:
上锁后,如果不设置release()进行解锁的话,就会产生死锁。
import threading
mutex = threading.Lock()
def sum_num1():
num = 1
# 2. 上锁
mutex.acquire()
if num == 1:
# 这里返回了空字符串,下面的release()方法没执行,因此会造成死锁。
# 因为第一个线程死锁了,因此第二个线程不会被执行的!!
return ''
# 3. 解锁(如果不解锁,就会产生死锁问题)
mutex.release()
def sum_num2():
mutex.acquire()
print("sum_num2:")
mutex.release()
if __name__ == '__main__':
sub_num1 = threading.Thread(target=sum_num1)
sub_num2 = threading.Thread(target=sum_num2)
sub_num1.start()
sub_num2.start()
以上是关于Python 多进程和多线程 的使用的主要内容,如果未能解决你的问题,请参考以下文章