深入理解python的多线程,多进程和协程(多进程)

1.前言

  1.  什么是多线程,多进程

  2.  GIL锁

2.多线程

  1. 多线程开发

  2. 线程安全

  3. 线程锁

  4. 死锁

  5. 线程池


以上是我们上一期学习的内容,这一期我们从下面开始


3.多进程

  1. 进程的三大模式

  2. 进程的常见功能

  3. 进程锁

  4. 进程池

4.协程


多进程:

首先我们创建进程需要依赖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组运行,这就是进程池及其作用。


总结:

关于进程的所有知识点都在这里了,如果想看线程,及其他可以点击我的主页去查看,关于协程,涉及的知识点太过丰盛了,下一期再给大家说吧,希望这期对大家有所帮助,我们下期见。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值