进击のpython




并发编程——进程方法


开启了进程之后,就要学习一下对应的方法

本小节对进程的一些方法进行简单的理解:

1.Process的join方法

2.Process的terminate与is_alive

Process的join方法

p.join([timeout]):主进程等待p终止(强调:是主进程处于等的状态,而p是处于运行的状态)

timeout是可选的超时时间

首先,系统在运行的过程中可能会出现这样的情况:

1.主进程和子进程彼此独立,在都完成运行之后,由系统进行统一回收

2.主进程等子进程运行完毕之后再执行

第一种情况好说,第二种情况就需要有一种机制能够让主进程检测子进程是否运行完毕

在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用

from multiprocessing import Process


def func(name, *args, **kwargs):
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p = Process(target=func, args=('子进程',))
    p.start()
    p.join()
    print('我是主进程...')

from multiprocessing import Process


def func(name, *args, **kwargs):
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p = Process(target=func, args=('子进程',))
    p.start()
    p.join()
    print('我是主进程...')

在没有利用join方法的时候,执行顺序是这样的

我是主进程...
子进程执行!
我是主进程...
子进程执行!

利用join之后,执行顺序就变成

子进程执行!
我是主进程...
子进程执行!
我是主进程...

可以看到,主进程的代码在等待子进程代码运行结束才开始执行

那是变成串行了吗???

import time
from multiprocessing import Process


def func(name, times, *args, **kwargs):
    time.sleep(times)
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p1 = Process(target=func, args=('子进程1', 1))
    p2 = Process(target=func, args=('子进程2', 2))
    p3 = Process(target=func, args=('子进程3', 3))

    p1.start()
    p2.start()
    p3.start()
    start_time = time.time()
    p1.join()
    p2.join()
    p3.join()
    print(f'子进程花费时间:{time.time()-start_time}')
    print('我是主进程...')

import time
from multiprocessing import Process


def func(name, times, *args, **kwargs):
    time.sleep(times)
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p1 = Process(target=func, args=('子进程1', 1))
    p2 = Process(target=func, args=('子进程2', 2))
    p3 = Process(target=func, args=('子进程3', 3))

    p1.start()
    p2.start()
    p3.start()
    start_time = time.time()
    p1.join()
    p2.join()
    p3.join()
    print(f'子进程花费时间:{time.time()-start_time}')
    print('我是主进程...')

如果将join理解成串行,那么,子程序的执行时间应该是1+2+3 = 6s多

先执行p1,睡1s,再执行p2,睡2s,再执行p3,睡3s

那我们来看一下执行结果:

子进程1执行!
子进程2执行!
子进程3执行!
子进程花费时间:3.270955801010132
我是主进程...

时间是3s多,说明不是串行,依旧是并发执行

在开始介绍过,join是主进程等待子进程运行结束,再运行

p1 p2 p3 都是子进程,彼此不需要等待,是并发执行的状态

所以子进程互相都是共享时间的,都是在执行的

而当子进程中3s的执行完了,也就意味着所有的子进程执行完毕了

才会执行主进程,所以子进程的执行时间只有3s多

上述的代码也可以优化一下:

import time
from multiprocessing import Process


def func(name, times, *args, **kwargs):
    time.sleep(times)
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p1 = Process(target=func, args=('子进程1', 1))
    p2 = Process(target=func, args=('子进程2', 2))
    p3 = Process(target=func, args=('子进程3', 3))
    ls = [p1, p2, p3]
    for l in ls:
        l.start()
    start_time = time.time()
    for l in ls:
        l.join()
    print(f'子进程花费时间:{time.time()-start_time}')
    print('我是主进程...')

import time
from multiprocessing import Process


def func(name, times, *args, **kwargs):
    time.sleep(times)
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p1 = Process(target=func, args=('子进程1', 1))
    p2 = Process(target=func, args=('子进程2', 2))
    p3 = Process(target=func, args=('子进程3', 3))
    ls = [p1, p2, p3]
    for l in ls:
        l.start()
    start_time = time.time()
    for l in ls:
        l.join()
    print(f'子进程花费时间:{time.time()-start_time}')
    print('我是主进程...')

Process的terminate与is_alive

p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方 法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁

p.is_alive():如果p仍然运行,返回True

import time
from multiprocessing import Process


def func(name, *args, **kwargs):
    print(f'{name}执行!')
    pass


if __name__ == '__main__':
    p = Process(target=func, args=('子程序',))
    p.start()
    p.terminate()
    print(p.is_alive())
    time.sleep(1)
    print(p.is_alive())
True
False

需要强调的一点就是terminate()会终止进程,但是不是立马就会将其回收

所以紧接着is_alive()也可能会返回True

隔一段时间再看,就返回的是False

这里提到了一个名词:僵尸进程,其实还有个孤儿进程

僵尸进程:

主进程还没结束的时候退出的子进程就是僵尸进程

任何一个子进程在结束之后并不会马上消失掉,而是要留下一个称为僵尸进程的数据结构,等待父进程处理

这是每个子进程在结束时都要经过的阶段,如果子进程在结束之后,父进程没有来得及处理

那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的

如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,应当避免

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程

孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作

孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上

init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为ini

这样,当一个孤儿进程凄凉地结束了其生命周期的时候

init进程就会代表党和政府出面处理它的一切善后工作,因此孤儿进程并不会有什么危害