- 什么是多线程,多进程
- GIL锁
- 多线程开发
- 线程安全
- 线程锁
- 死锁
- 线程池
以上是我们上一期学习的内容,这一期我们从下面开始
3.多进程
- 进程的三大模式
- 进程的常见功能
- 进程锁
- 进程池
多进程:
首先我们创建进程需要依赖multiprocessiong模块,如果要用这个模块需要了解进程的三大模式
- 进程的三大模式:
fork | fork会拷贝主进程的所有资源然后会交给新的进程,并且支持文件对象和线程锁的传输(快) | linux系统特有的,任意位置开始执行 |
spawn | 会传递run函数内的必备资源,并且不支持文件对象和线程锁的传输(慢) | linux,win都含有,会从main函数代码块开始执行 |
forkserver | 会传递run函数内的必备资源,并且不支持文件对象和线程锁的传输(慢) | 只有部分linux含有,会从main函数代码块开始执行 |
然后大家要知道,要创建子进程需要导入multiprocessiong模块,上代码
import multiprocessing
然后可以在这个模块里切换模式,切换模式时要注意,需要看上面的表,来知道你所在的 *** 作系统内有没有这个模式,一般开发者都在linux,模式都是有的,但win只有一种,切换代码如下:
if __name__ == '__main__':
multiprocessing.set_start_method("要切换的模式")
注意每个系统尽量都把进程放到main函数代码块内。
小编下面用win的spawn模式来个大家展示,首先先看一段代码:
import multiprocessing
import time
def task():
print(name)
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
name = []
p1 = multiprocessing.Process(target=task)
p1.start()
这时看到我的模式是spawn,我在主线程里定义了一个name为空列表,但spawn不会帮我们拷贝,于是运行就会发现错误
就是说在我的子线程内没有这个name,那我i们应该怎么办?
就是通过参数给子进程传一个参数看下面代码
import multiprocessing
import time
def task(data):
print(data)
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
name = []
p1 = multiprocessing.Process(target=task, args=(name,))
p1.start()
这时我们发现我在创建子线程的时候通过args传了一个name进去,在task函数接收一下,这回再运行
这时我们的问题就解决了
进程的常见功能 :
首先进程的常见功能和线程的功能差不多少,具体看下表
.start() | 进程准备完毕,等待cpu调度 |
.join() | 等子进程结束,主进程才能往下运行 |
.daemon(逻辑布尔值) | 守护进程 |
.daemon(Ture) | 守护进程—主进程运行完,子线程随之关闭 |
.daemon(False) | 非守护进程—主进程需要等子进程结束后才能结束 |
综上,是不是与线程很类似呢·
- 进程间的数据共享
我们知道进程间的数据也就是资源是独立的,那我们怎样才能让进程间的数据共享呢?
在python中给我们提供了4种方式
基于value和Array进行数据共享:这个方法用起来会非常的舒服,因为python的底层是用c来开发的,这个方法用了些c语言的方法
这个方法不要求大家一定要回,因为我们很少用,这个可以当了解
'c' | ctypes.c_char | 'u' | ctypes.c_wchar |
'b' | ctypes.c_byte | 'B' | ctypes.c_ubyte |
'h' | ctypes.c_short | 'H' | ctypes.c_ushort |
'i' | ctypes.c_int | 'I' | ctypes.c_uint |
'l' | ctypes.c_long | 'L' | ctypes.c_ulong |
'f' | ctypes.c_float | 'd' | ctypes.c_double |
要使用这个方法的话我们要导入Value和Array,上代码
from multiprocessing import Process, Value, Array
def task(data):
data[0] = 666
if __name__ == '__main__':
# 在主进程创建了1个数据
array = Array('i', [11, 22, 33, 44])
p1 = Process(target=task, args=(array,))
p1.start()
p1.join()
print(array[:])
运行一下:
给大家说一下原理:创建数据后,把数据传到子进程程,然后子进程就会调用子进程函数,把我的索引值0的数值修改了。其实大家不用真的会,理解就好,我们很少用这种方法的
基于Manger 建造一些字典和列表这样就相对简单了一些。直接上代码
from multiprocessing import Process, Manager
def task(data, inner):
data[1] = '1'
data['2'] = 2
data[0.25] = None
inner.append(666)
if __name__ == '__main__':
with Manager() as manger:
d = manger.dict()
l = manger.list()
p = Process(target=task, args=(d, l,))
p.start()
p.join()
print(d)
print(l)
运行一下
这样发现我们还是成功了,原理是,在主进程建立字典和列表,创建子进程的时候将字典和列表传入,那么子进程 *** 作的时候会把数据保存到字典和列表里,这样就可以实现传递
基于队列实现传递:这就是队列传输的原理,接着看代码
from multiprocessing import Process, Manager
import multiprocessing
def task(data):
for i in range(10):
# 在管的一端发送
data.put(i)
if __name__ == '__main__':
# 创建一个队列,就是那个管道
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=task, args=(queue,))
p.start()
p.join()
print("主进程")
j = 0
# 在管道的一端接
while j < 10:
print(queue.get())
运行一下:
这样我们也完成了传输,我们可以建立队列,一端put,一端get这就是原理
基于Pipes来实现直接上代码
import multiprocessing
import time
def task(cv2):
time.sleep(1)
cv2.send([1, 2, 3, 4])
# 进行阻塞
data = cv2.recv()
print("子进程接收", data)
time.sleep(2)
if __name__ == '__main__':
parent_cv2, child_cv2 = multiprocessing.Pipe()
p = multiprocessing.Process(target=task, args=(child_cv2,))
p.start()
# 进行阻塞
info = parent_cv2.recv()
print("主线程接收", info)
parent_cv2.send(6)
和队列差不多相似,把管道传过去,双方都接收recv和send发出
总结:这就是以上的4中方式还有一些方式通过第三方软件传输,比如数据库等,我们接着往下进行
进程锁:
和线程锁一样,都是为防止对统一数据进行 *** 作的时候,防止信息切换时发生错误,这就有了进程锁。
这段代码需要一个文件我带着大家创建一下名字为1,里面保存一个数字10
接着上源码:
import multiprocessing
import time
def task(lock):
# 第一个进程获取锁,没获取锁的等待复原锁之后才能运行
lock.acquire()
# 打开文件一个进程进来就让他减去1
with open('1.txt', mode='r', encoding='utf-8') as f:
curren_num = int(f.read())
time.sleep(0.5)
curren_num -= 1
with open('1.txt', mode='w', encoding='utf-8') as f:
f.write(str(curren_num))
# 复原锁
lock.release()
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
# 建立进程锁
lock = multiprocessing.RLock()
for i in range(10):
# 开启10个进程
p = multiprocessing.Process(target=task, args=(lock,))
p.start()
代码上有注释,运行一下
一开始文件内有10
运行后:
发现运行成功了,这就时进程锁,和线程锁基本一样,那锁就到这里
进程池:
在线程的时候我们说过,如果无休止的创建线程,速度不会加快,反而会让我们减慢,在那里我们引入了线程池,这里我们引用出进程池
原理:和线程池一样,进程池也是创建出个数,以个数为一对,分批进行,这就是进程池,直接上代码
首先我们要导入ProcessPoolExecutor函数
from concurrent.futures import ProcessPoolExecutor
然后看整个代码
import time
from concurrent.futures import ProcessPoolExecutor
def task(num):
print("执行", num)
time.sleep(2)
if __name__ == '__main__':
# 建立进程池,进程数量为4
pool = ProcessPoolExecutor(4)
for i in range(10):
# submit是进程准备好了,随时等待cpu调度,括号里(函数名,参数1,参数2,.......)
pool.submit(task, i)
运行一下
一共用range内置函数生成9个进程,但进程池里只有4个进程,所以被分为3组运行,这就是进程池及其作用。
总结:
关于进程的所有知识点都在这里了,如果想看线程,及其他可以点击我的主页去查看,关于协程,涉及的知识点太过丰盛了,下一期再给大家说吧,希望这期对大家有所帮助,我们下期见。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)