欢迎来到赛兔子家园

进程

什么是进程?

进程:正在进行的一个过程或者说一个任务。而负责执行任务则是CPU。

进程与程序的区别

程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。

并发与并行

无论是并发还是并行,在用户看来都是【同时】运行的,无论是进程还是线程,都只是一个任务而已,真正干活的是CPU,CPU来做这些任务,而一个CPU同一时刻只能执行一个任务。

并发:伪并行,即看起来是同时运行。单个CPU+多道技术就可以实现并发。

并行:同时运行,只有具备多个CPU才能实现并行。

创建进程: 函数式(常用方法)
from multiprocessing import Process
import time


def fun():
    print("我是fun函数")


def task(name):
    print("{0}运行".format(name))
    time.sleep(3)
    fun()
    print("{0}完成了".format(name))


if __name__ == '__main__':
    p = Process(target=task, args=("马爷",))
    p.start()
    print("====主进程")
 Python并发编程:多进程

验证进程之间的空间隔离

#例1:
from
multiprocessing import Process import time x = 100 def task(name): global x x = 0 if __name__ == '__main__': p = Process(target=task, args=("马爷",)) p.start() print(x) # 此时x为100,因为主进程先执行了
# 例2 # 子进程先执行,主进程后执行通过打印输出验证空间是相互隔离的 from multiprocessing import Process import time x = 100 def task(name): global x x = 0 print("子进程x", x) # 子进程x=0 if __name__ == '__main__': p = Process(target=task, args=("马爷",)) p.start() time.sleep(3) print(x) # 此时x为100 # 例3 from multiprocessing import Process import time x = [] def task(name): x.append(100) print("子进程x", x) # 子进程[100] if __name__ == '__main__': p = Process(target=task, args=("马爷",)) p.start() time.sleep(3) print(x) # 此时x为[]

join的使用

join让父进程等待子进程结束完,父进程才可以结束

例1:开启一个子进程

from multiprocessing import Process
import time

x = 1000


def task(name):
    global x
    x = 0
    print("子进程x", x)  # 子进程[100]


if __name__ == '__main__':
    p = Process(target=task, args=("马爷",))
    p.start()
    p.join()  # 父进程在原地等待
       print("父进程", x)  # 此时x为[]

例2:开启多个子进程

from multiprocessing import Process
import time

x = 1000

def task(num):
    global x
    x = 0
    print("子进程{0}!".format(num))


if __name__ == '__main__':
    for i in range(10):
        p = Process(target=task, args=(i,))
        p.start()
    print("主进程结束了")

打印输出:
主进程结束了
子进程0!
子进程1!
子进程2!
子进程3!
子进程4!
子进程6!
子进程7!
子进程8!
子进程5!
子进程9!

为什么主进程在子进程前面执行了?start只是向操作系统发出命令,而不是时马上开始,操作系统还需要从内存在开空间,开进程,进程开闭的损耗是很大的。所有主进程不会最后执行。

开启三个进程测试:

from multiprocessing import Process
import time

x = 1000

def task(num):
    print("子进程开始了{0}".format(num))
    time.sleep(num)


if __name__ == '__main__':
    p1 = Process(target=task, args=(1,))
    p2 = Process(target=task, args=(2,))
    p3 = Process(target=task, args=(3,))
    # 发三个信号,启动3个进程 6秒还是3秒
    p1.start()
    p2.start()
    p3.start()
    print("主进程结束了")  

3个进程运行3秒左右。

 精简版:

from multiprocessing import Process
import time


def task(num):
    print("子进程开始了{0}".format(num))
    time.sleep(num)


if __name__ == '__main__':
    start_time = time.time()
    p_l1 = []
    for i in range(1, 4):
        p = Process(target=task, args=(i,))
        p_l1.append(p)
        p.start()
    for i in p_l1:
        i.join()

    print("主进程开始了{0}!".format(time.time() - start_time))
    # 输出:主进程开始了3.159229278564453!
 僵尸进程与孤儿进程

由于子进程没有结束,所有主进程需要等待子进程结束之后才可以结束。

为什么主进程不结束?

主进程要监听子进程状态变化,等子进程结束之后的一段时间内,对其回收。

由于主进程与子进程是异步的过程,主进程与子进程的开启与结束相互都没有关联,主进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么父进程则无法获取子进程的

状态信息。那么如何保证主进程可以在任意时刻获取子进程的结束时的状态呢?

unix提供了一个机制,在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号,退出状态,运行时间等)直到父进程通过wait/waitpid来获取时才释放。

什么是僵尸进程?

所有的子进程结束之后,在被父进程通过wait/waitpid回收之前,都称为僵尸进程(通过命令获取其运行状态为Z)。

僵尸进程的危害?如果主进程不调用wait/waitpid的话,那么保留的那段信息就不会释放,其进程号一直被占用,但是系统所能使用的进程号是有限的,如果产生大量的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,这就是僵尸进程的危害。

什么是孤儿进程?

孤儿进程,一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。

僵尸进程如何解决?

例如有个进程,它定期产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短。但是父进程只管生成新的子进程,至于子进程退出之后的事情,一概不问。这样,系统运行一段时间之后,系统中就会存在很多的僵尸进程。严格来说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵尸进程的那个父进程,把该父进程杀掉。那么它产生的僵尸进程就变成了孤儿进程,孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源。

守护进程

守护进程,例如:皇帝是太监的守护,皇帝如果死了,一般老太监也跟着自杀了。

主进程死了,守护进程也跟着死掉了。

将子进程设置成守护进程,它会等待主进程结束之后,马上结束:

from multiprocessing import Process
import time


def task(name):
    print("{0}运行了".format(name))
    time.sleep(5)
    print("{0}完成了".format(name))


if __name__ == '__main__':
    p = Process(target=task, args=("马爷",), name="任务1")
    p.daemon = True  # 子进程设置为守护进程
    p.start()
    print("===主进程")
    # 输出:===主进程

结果主进程结束后马上结束打印输出:===主进程

 互斥锁Lock

join和Lock共同点:都是将并发变成串行,从而保证有序进行。

并发时我们需要输出结果是有序的,效率第二。

这个时候使用互斥锁让进行平等竞争。

例如:

模拟三个员工同时向打印机发出请求,打印三份资料。

这个时候效率第二,输出结果变成串行,打印机需要先打印第一个人的结果,然后在打印第二个结果,第三个结果;

谁先到达谁先开始,通过Lock锁来完成,三个员工也就是三个进程谁先抢到Lock谁先执行。

例:通过Lock锁让tasek1执行完毕后,再执行task2,task3

from multiprocessing import Process
from multiprocessing import Lock
import time
import random


def task1(lock):
    lock.acquire() # 获得锁
    print("task1开始执行")
    time.sleep(random.randint(1, 3))
    print("task1:执行完毕")
    lock.release() # 释放锁


def task2(lock):
    lock.acquire()
    print("task2开始执行")
    time.sleep(random.randint(1, 3))
    print("task2:执行完毕")
    lock.release()


def task3(lock):
    lock.acquire()
    print("task3开始执行")
    time.sleep(random.randint(1, 3))
    print("task3:执行完毕")
    lock.release()


if __name__ == '__main__':
    mutex = Lock()
    p1 = Process(target=task1, args=(mutex,))
    p2 = Process(target=task2, args=(mutex,))
    p3 = Process(target=task3, args=(mutex,))
    # 锁必须是同一个,主进程创建一个共同锁,然后通过参数的形式传递进取
    p1.start()
    p2.start()
    p3.start()

打印输出:

task1开始执行
task1:执行完毕
task2开始执行
task2:执行完毕
task3开始执行
task3:执行完毕

抢票系统

需求:

用互斥锁模拟12306抢票系统10个人同时抢票,10个人都有查票的功能,然后谁先开始买票时,其他人只能等待,先查票,在买票。进程之间理论上来说是不可以共享内存数据的,但是可以共用一个硬盘,所以我们的数据应该放在一个文件中。

from multiprocessing import Process
from multiprocessing import Lock
import time
import random
import json
import os


def search():
    """查车票"""
    time.sleep(random.randint(1, 3))
    # 先模拟一下延迟效果
    with open("db.json", encoding="utf-8") as f1:
        dic = json.load(f1)
        print("剩余票数为:{0}".format(dic.get("count")))


def get():
    with open("db.json", encoding="utf-8") as f1:
        dic = json.load(f1)
    if dic.get("count") > 0:
        dic['count'] -= 1
        time.sleep(random.randint(1, 3))
        with open("db.json", "w+", encoding="utf-8") as f2:
            json.dump(dic, f2)
        print("{0}购票成功".format(os.getpid()))


def task(lock):
    search()  # 查询
    lock.acquire()
    get()  # 买票
    lock.release()


if __name__ == '__main__':
    mutex = Lock()
    for i in range(10):
        p = Process(target=task, args=(mutex,))
        p.start()

db.json

{"count": 5}

将查票变成并发,但是抢票就是串行了,谁先付款谁先买票。互斥锁比join的优势,join是将所有进程变成串行并且必须按照你之前规定的顺序。互斥锁也是按照顺序但是是随机的顺序谁先开启进程,谁先加入。

所以修改数据的时候必须要串着来,互斥锁可以让一部分代码(修改共享数据的部分)串行,join只能让代码整体串行。

 

posted on 2021-02-05 09:06  赛兔子  阅读(86)  评论(0编辑  收藏  举报

导航