栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

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

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深入理解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组运行,这就是进程池及其作用。


总结:

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/831386.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号