20181228 并发编程初步

 

 

并发编程

并发即同时发生,并发编程指程序中的多个任务同时被处理,其基于多道技术。

 

并发与并行

并发指的是多个事件同时发生,也称为伪并行(并发事实上是交替进行,如果切换速度足够快,那么就可以让人觉得是在同时发生)

并行指的是多个事件同时进行

 

阻塞与非阻塞

阻塞:程序遇到I/O操作或是sleep,导致后续代码不能被CPU执行的状态。

非阻塞:代码正常被CPU执行。

 

多道技术:

空间复用:内存分成几个部分,不同部分运行不同程序,彼此之间独立。 时间复用:当一个程序执行非计算操作(如I/O,等待用户输入;或操作系统强制切换),将CPU切到另一个程序进行计算操作。

进程的三种状态:就绪态,运行态,阻塞态

多道技术会在进程执行时间过长或遇到I/O时自动切换到其他进程,原进程被剥夺CPU执行权,需要重新进入就绪态后才能等待执行。

 

 

进程:正在运行的程序就是进程,是一种抽象的概念。 进程依赖于操作系统,一个进程代表着一份资源(内存等),进程间内存相互独立,一个程序可以有多个进程。

# 子进程中的数据修改,不会影响父进程,即子进程和父进程之间相互独立(子进程在运行前会拷贝父进程的环境,所以在子进程运行前要初始化好父进程的环境)
import time
a = 100
def task():
   global a
   a = 0  # 子进程中的全局变量a已经变为0,但是父进程中的全局变量a,子进程无权修改。
   print("子进程",a)

if __name__ == '__main__':
   p = Process(target=task)
   p.start()
   time.sleep(1)  # 注意此处的时间停顿,此处引出join函数(子进程全部结束再执行父进程),详见最后。
   print("主程序",a)
输出结果为:
子进程 0
主程序 100

与上面的做一下对比:

两次结果的差异在于:子进程需要载入才能运行,这时候CPU可以进行主程序的其他内容操作。如果主程序睡眠,那么睡眠时间就够子程序执行完了。所以在输出顺序上存在差异。

import time
a = 100
def task():
   global a
   a = 0
   print("子进程",a)

if __name__ == '__main__':
   p = Process(target=task)
   p.start()
   print("主程序",a)
输出结果为:
主程序 100
子进程 0

 

 

PID与PPID

PID:操作系统给每个进程编号,这个编号就是PID

PPID:当进程A开启了另一个进程B,A称为B的父进程,B称为A的子进程,操作系统可以看做是所有主进程的父进程。

import os
while True:
   print("haha")
   print("self",os.getpid())  # 获取self的进程ID,ID号由操作系统分配
   print("parent",os.getppid())  # 第一个p指的是parent,即父进程的ID
   break
# 每次的ID号都会不同,pid是当前执行程序,如果这段代码放到pycharm中运行,ppid就是pycharm的编号,如果这段代码放到cmd中运行,ppid就是cmd的编号。

 

孤儿进程和僵尸进程:
孤儿进程:父进程已经终结,但子进程仍在运行,无害,操作系统会负责回收处理等操作。
僵尸进程:子进程执行完毕,残留一些信息(如进程id等)。但父进程没有处理残留信息,
导致残留信息占用内存,有害。

 

了解

操作系统的两个核心作用: 1、屏蔽了复杂繁琐的硬件接口,为程序提供了简单易用的系统接口 2、将应用程序对硬件资源的竞争变成有序的使用。

操作系统与普通应用程序的区别: 1、操作系统不能轻易被用户修改 2、操作系统源代码量巨大,仅内核通常就在五百万行以上 3、长寿,一旦完成不会轻易重写。

 

 

python中开启子进程的两种方式

方式1:

实例化Process类

from multiprocessing import Process
import os

def task():
   print("self",os.getpid())  # 自己的id
   print("parent",os.getppid())  # 父进程的id,此处的父进程是下面的p的那个进程(由p启动)
   print("task run")
# win下创建子进程时,子进程会将父进程的代码加载一遍,导致重复创建子进程
# 所以一定要将创建子进程的代码放到main的下面。
if __name__ == '__main__':
   print("self",os.getpid())  # 当前执行内容的id
   print("parent",os.getppid())  # 通过pycharm执行,所以此处的父进程id是pycharm的id
   p = Process(target=task, name="子进程")  # 主进程启动子进程,目标子进程为task(注意此处仅为函数名)。创建一个表示进程的对象,并不是真正的创建进程,Process是一个类,他还有很多参数和方法,具体可以ctrl点进去查看。
   p.start()  # 向操作系统发送请求,操作系统会申请内存空间,然后把父进程的数据拷贝给子进程,作为子进程的初始状态(但你爹永远是你爹(父进程),接产大夫(操作系统)不会成为隔壁老王)

方式2:

继承Process类 并覆盖run方法,子进程启动后会自动执行run方法。

这种方法可以自定义进程的属性和行为,拓展对象的功能。

class MyProcess(Process):  # 继承Process类
   def __init__(self,url):
       super().__init__()
       self.url = url
       
   def run(self):   # 覆盖run方法
       print("正在下载文件。。。",self.url)
       print("runrunrun")

if __name__ == '__main__':
   p = MyProcess("www.baidu.com")
   p.start()  # 实例化了Process对象,start自动调用run。

需要注意的是 在windows下 开启子进程必须放到__main__下面,因为windows在开启子进程时会重新加载所有的代码造成递归

 

join函数

调用start函数后的操作就由操作系统来玩了,至于何时开启进程,进程何时执行,何时结束都与应用程序无关,所以当前进程会继续往下执行,join函数可以让父进程等待子进程结束后再执行。(即提高子进程优先级)

案例1:

from multiprocessing import Process
import time

x=1000

def task():
   time.sleep(3)
   global x
   x=0
   print('儿子死啦',x)
if __name__ == '__main__':
   p=Process(target=task)
   p.start()
   p.join() # 让父亲在原地等,子进程执行完毕才会执行下面的代码。
   print(x)

案例2:

from multiprocessing import Process
import time
def task(num):
   print("我是%s进程" %num)
   time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
   print("我是%s进程" %num)

if __name__ == '__main__':
   start_time = time.time()
   ps = []
   for i in range(5):
       p = Process(target=task,args=(i,))  # 注意此处传参方式!元组形式
       p.start()
       ps.append(p)
   for p in ps:
       p.join()
   print("over")
   print(time.time()-start_time)
输出结果:
我是0进程
我是1进程
我是2进程
我是0进程
我是3进程
我是1进程
我是4进程
我是2进程
我是3进程
我是4进程
over
5.9241626262664795  #正常应该是2秒多,我电脑太垃圾了,所以卡的接近6秒。


也可以写成:
from multiprocessing import Process
import time
def task(num):
   print("我是%s进程" %num)
   time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
   print("我是%s进程" %num)

if __name__ == '__main__':
   start_time = time.time()
   for i in range(5):
       p = Process(target=task,args=(i,))
       p.start()
   p.join()  # 提高子进程优先级,CPU优先处理子进程,父进程放到后面
   print("over")
   print(time.time()-start_time)
输出结果为:
我是0进程
我是1进程
我是2进程
我是0进程
我是3进程
我是1进程
我是4进程
我是2进程
我是3进程
我是4进程
over
5.880282878875732

如果改写成这样:

from multiprocessing import Process
import time
def task(num):
   print("我是%s进程" %num)
   time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
   print("我是%s进程" %num)

if __name__ == '__main__':
   start_time = time.time()
   for i in range(5):
       p = Process(target=task,args=(i,))
       p.start()
       p.join()  # 提高子进程优先级,CPU优先处理子进程,父进程放到后面,此处为只有当前子进程结束后,才会继续循环。可以理解为for循环还是属于父进程的内容。
   print("over")
   print(time.time()-start_time)
输出结果为:
我是0进程
我是0进程
我是1进程
我是1进程
我是2进程
我是2进程
我是3进程
我是3进程
我是4进程
我是4进程
over
14.754556894302368  #正常应该是10秒多一点,我这戴尔垃圾太卡,果然是傻多戴。

 

Process对象常用属性
from multiprocessing import Process
def task(n):
   print('%s is runing' %n)
   time.sleep(n)

if __name__ == '__main__':
   start_time=time.time()

   p1=Process(target=task,args=(1,),name='任务1')  # 注意传参方式
   p1.start()
   print(p1.pid)  # 获取进程编号
   print(p1.name)  # 获取进程属性
   p1.terminate()  # 结束进程
   p1.join()  # 提高进程优先级
   print(p1.is_alive())  # 判断进程是否存在
   print('主进程')

 

 

原文地址:https://www.cnblogs.com/realadmin/p/10192721.html