06.系统编程-1.进程

Posted cjr0707

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了06.系统编程-1.进程相关的知识,希望对你有一定的参考价值。

1、多任务的引入

实现类似唱歌又跳舞的任务同时进行的,叫做:多任务

2、多任务的概念

什么叫“多任务”。简单地说, 就是操作系统可以同时运行多个任务。

单核CPU执行多任务,就是操作系统轮流让各个任务交替执行,任务1执行0.01秒, 切换到任务2,任务2执行0.01秒,再切换到任务3, 执行0.01秒……这样反复执行下去。每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了, 我们感觉就像所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

3、进程的创建-fork

3.1进程vs程序

编写完毕的代码,在没有运行的时候,称之为程序。

正在运行着的代码,就成为进程。

进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的。

3.2fork( )

os模块封装了fork函数,可以在Python程序中轻松创建子进程

import os
#注意,fork函数,只在Unix/Linux/Mac上运行,Windows不可以
pid = os.fork()
if pid == 0:
    print(‘哈哈111‘)
else:
    print(‘哈哈222‘)

说明

  • 程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
  • 父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的 id号。
  • 在Unix/Linux操作系统中,提供了一个fork()系统函数,它非常特殊。
  • 普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。
  • 这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

3.3getpid()、getppid()

可以理解为getppid——get parents pid 的缩写

import os

rpid = os.fork()
if rpid<0:
    print("fork调用失败。")
elif rpid == 0:
    print("我是子进程(%s),我的父进程是(%s)"%(os.getpid(),os.getppid()))
else:
    print("我是父进程(%s),我的子进程是(%s)"%(os.getpid(),rpid))

print("父子进程都可以执行这里的代码")

运行结果:
我是父进程(19360),我的子进程是(19361)
父子进程都可以执行这里的代码
我是子进程(19361),我的父进程是(19360)
父子进程都可以执行这里的代码

4、多进程修改全局变量

#coding=utf-8
import os
import time

num = 0
# 注意, fork函数, 只在Unix/Linux/Mac上运行, windows不可以
pid = os.fork()
if pid == 0:
        num+=1
        print(‘哈哈1---num=%d‘%num)
else:
        time.sleep(1)
        num+=1
        print(‘哈哈2---num=%d‘%num)

总结:

  • 多进程中,子进程相当于是主进程的一个深拷贝
  • 每个进程中所有数据(包括全局变量)
  • 都各有拥有一份,互不影响

5、多次fork问题

如果在一个程序,有2次的fork函数调用, 是否就会有3个进程呢?

#coding=utf-8
import os
import time

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
pid = os.fork()
if pid == 0: #if下是子进程的操作,因为子进程的返回值永远是0
    print(‘haha...1,我是%s,我的父进程是%s‘%(os.getpid(),os.petppid))
else: #else下是主进程操作,因为主进程的返回值是其子进程的pid
    print(‘haha...,我是主进程%s‘%os.getpid)

pid = os.fork()
if pid == 0:
    print(‘haha...2,我是%s,我的父进程是%s‘%(os.getpid(),os.petppid))
else:
    print(‘haha...,我是主进程%s‘%os.getpid)

time.sleep(1)

运行结果:
haha...,我是主进程9584
haha...,我是主进程9584
haha2...,我是9586,我的父进程是9584
haha1...,我是9585,我的父进程是9584
haha...,我是主进程9585
haha2...,我是9587,我的父进程是9585

仔细看上述代码和运行结果,程序的执行结果并不是你想象的打印了四次,在这里需要说明的是,当主进程(9584)执行到第一条fork语句时,要创建一个子进程,在这个时候,会将主进程的所有内容,复制一份给子进程(9585),也就说,子进程(9585)中也会包含主进程(9584)中的第二条fork语句,即子进程(9585)也会再创建子进程(9587),主进程(9584)执行到第二条fork语句时,会再创建一个子进程(9586),这样就导致了这段代码会有六次的print,这样说可能依然不是很清楚,下面画一个简单的进程关系图帮助理解:

graph LR
主进程9584-->子进程9585
子进程9585-->子进程9587
主进程9584-->子进程9586

技术分享图片

说明:

主进程、 子进程的执行顺序没有规律, 完全取决于操作系统的调度算法


接下来的在Windows、Linux中都可以执行多进程

6、multiprocessing

multiprocessing模块就是跨平台版本的多进程模块。

multiprocessing模块提供了一个Process类来代表一个进程对象。

Process语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
- target:表示这个进程实例所调用对象;
- args:表示调用对象的位置参数元组;
- kwargs:表示调用对象的关键字参数字典;
- name: 为当前进程实例的别名;
- group:多数情况下用不到;

6.1Process类常用方法

  1. is_alive():判断进程实例是否还在执行;
  2. join([timeout]):可以等待进程结束后再继续往下运行,或等待多少秒,通常用于进程间的同步;
  3. start():启动进程实例( 创建子进程) ;
  4. run():如果没有给定target参数, 对这个对象调用start()方法时, 就将执行对象中的run()方法;
  5. terminate():不管任务是否完成,即终止;

6.2Process类常用属性

  1. name: 当前进程实例别名, 默认为Process-N, N为从1开始递增的整数;
  2. pid: 当前进程实例的PID值;

6.3使用multiprocessing里的Process创建进程的第一种方式

from multiprocessing import Process
from time import sleep

#子进程
def sing(param1,param2,**kwargs):
    print("%s,%s"%(param1,param2))
    print(kwargs)
    k = 0
    while k<5:
        print("sing %d..."%k)
        sleep(1)
        k +=1

if __name__ == ‘__main__‘:
    print("main process run")
    #定义子进程:唱歌
    p = Process(target=sing,args=(20,"xxx"),kwargs={"key1":"mmm","key2":"nnn"})
    #子进程的启动
    p.start()
    #p.join() 如果加上加上join主进程就不会进入while循环,要等到子进程运行结束
    #不加的话,子进程与主进程互不干扰,都在执行

    #主进程:玩游戏
    while True:
        print("gaming...")
        #停止0.5秒
        sleep(0.5)

    print("child process is over")
    print("main poress end")
    
运行结果:
main process run
gaming...
20,xxx
{‘key2‘: ‘nnn‘, ‘key1‘: ‘mmm‘}
sing 0...
gaming...
gaming...
sing 1...
gaming...
gaming...

7、使用multiprocessing里的Process创建进程的第二种方式——继承Process的类

  • 创建新的进程还能够使用类的方式,可以自定义一个类, 继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象
from multiprocessing import Process
from time import sleep

#定义进程类,继承Process
class Music(Process):
    def __init__(self,name):
        #调用父类的初始化方法
        #因为Process类本身也有__init__方法, 这个子类相当于重写了这个方法
        #但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个
        #最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作
        Process.__init__(self)
        self.name = name

    #重写Process类的run方法
    def run(self):
        print(self.name)
        k = 0
        while True:
            print("sing %d..." % k)
            sleep(0.5)
            k += 1


if __name__ == ‘__main__‘:
    print("main process run")
    #创建新的进程,定义进程对象
    p = Music(‘xxx‘)
    #对一个不包含target属性的Process类执行start()方法
    #就会运行这个类中的run()方法
    p.start()

    # 主进程:玩游戏
    while True:
        print("gaming...")
        # 停止0.5秒
        sleep(0.5)

运行结果:
main process run
gaming...
xxx
sing 0...
gaming...
sing 1...
gaming...
sing 2...
gaming...
……

8、进程池 Pool

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行

8.1常用函数

multiprocessing.Pool常用函数解析:

  1. apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并执行,堵塞方式必须等待上一个进程退出才能执行下一个进程)
    【异步:一个任务不用等待其他任务执行完毕再执行,可以立刻开始,互不影响】
  2. args为传递给func的参数列表;
  3. kwds为传递给func的关键字参数列表;
  4. apply(func[, args[, kwds]]): 使进程阻塞方式调用func
  5. close():关闭Pool,使其不再接受新的任务;【协同步调,按照一定的顺序执行】
  6. terminate():不管任务是否完成,立即终止;
  7. join():主进程阻塞,等待子进程的退出,必须在close或terminate之后。

8.2apply堵塞式

from multiprocessing import Pool
from time import sleep

def kill():
    i=0
    while i<10:
        print("乔峰杀了一个挑战者...")
        sleep(1)
        i += 1

def attack(name):
    print("%s attack..."%name)
    sleep(1)

if __name__ == ‘__main__‘:
    #创建进程池,最大进程数5
    po = Pool(5)
    #向进程池添加子进程
    #Pool.apply_async(要调用的目标,(传递给目标的参数元组,))
    #每次循环将会用空闲出来的子进程去调用目标
    po.apply_async(kill)
    #po.apply(kill)


    for i in range(20):
        po.apply_async(attack,(‘掌门__‘+str(i),))
        #po.apply(attack,(‘zhangmen_‘+str(i),))

    po.close()  #关闭进程池, 关闭后po不再接收新的请求
    po.join()   #等待po中所有进程执行完成, 必须放在close语句之后
    #po.terminate()

执行代码:
乔峰杀了一个挑战者...
掌门__0 attack...
掌门__1 attack...
掌门__2 attack...
掌门__3 attack...
乔峰杀了一个挑战者...
掌门__4 attack...
掌门__5 attack...
掌门__6 attack...
掌门__7 attack...
……
如果不使用  po.apply_async
而使用    po.apply
其代码执行如下:
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
乔峰杀了一个挑战者...
掌门__0 attack...
掌门__1 attack...
掌门__2 attack...
……

9、进程间的通信Queue

Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序。

说明:

1、初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);

2、Queue.qsize():返回当前队列包含的消息数量;

3、Queue.empty():如果队列为空,返回True, 反之False ;

4、Queue.full():如果队列满了,返回True,反之False;

5、Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;Queue.get_nowait():相当Queue.get(False);

-----a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果为空, 此时程序将被阻塞( 停在读取状态) , 直到从消息列队读到消息为止,如果设置了timeout, 则会等待timeout秒, 若还没读取到任何消息, 则抛出"Queue.Empty"异常;

-----b) 如果block值为False, 消息列队如果为空, 则会?刻抛出"Queue.Empty"异常;

6、Queue.put(item,[block[,timeout]]):将item消息写入队列,block默认值为True;Queue.put_nowait(item):相当Queue.put(item, False);

-----a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果已经没有空间可写入, 此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒, 若还没空间,则抛出"Queue.Full"异常;

-----b) 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;

import time, random
from multiprocessing import Process, Queue


def write(q):
    print(‘write...%s‘ % (id(q)))
    for value in [‘A‘, ‘B‘, ‘C‘]:
        print(‘Put %s to queue...‘ % value)
        q.put(value)
        time.sleep(random.random())


def read(q):
    print(‘read...%s,num=%s,flag = %s‘ % (id(q), q.qsize(), q.empty()))
    while True:
        if not q.empty():
            value = q.get()
            print(‘Get %s from queue.‘ % value)
            time.sleep(3)


if __name__ == ‘__main__‘:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    # pw.join()
    # 启动子进程pr,读取:
    pr.start()
    # pr.join()

    print(‘game over...‘)

执行结果:
game over...
write...2800150905688
Put A to queue...
read...2215658980184,num=1,flag = False
Get A from queue.
Put B to queue...
Put C to queue...
Get B from queue.
Get C from queue.

9.1Queue实例

from multiprocessing import Queue

q = Queue(3)
print(q.empty())
q.put(33)
print(q.empty())
print(q.qsize())
q.put(55)
print(q.full())
print(q.qsize())
q.put(55)
print(q.full())
print(q.qsize())

print(q.get())
print(q.full())
print(q.qsize())

运行结果:
True
False
1
False
2
True
3
33
False
2

9.2进程池中的Queue

如果要使用Pool创建进程, 就需要使用multiprocessing.Manager()中的Queue(), 而不是multiprocessing.Queue(), 否则会抛异常。

import time,random,os
from multiprocessing import Pool,Manager


def reader(q):
    print("reader启动(%s),父进程为(%s)"%(os.getpid(),os.getppid()))
    for i in range(q.qsize()):
        print("reader从Queue获取到消息:%s"%q.get())

def writer(q):
    print("writer启动(%s),父进程为(%s)"%(os.getpid(),os.getppid()))
    for i in "yongGe":
        q.put(i)

if __name__=="__main__":
    print("(%s) start"%os.getpid())
    q=Manager().Queue() #使用Manager中的Queue来初始化
    po=Pool()
    # 使用阻塞模式创建进程
    # 这样就不需要在reader中使用死循环了,可以让writer完全执行完成后
    # 再用reader去读取
    po.apply(writer,(q,))
    po.apply(reader,(q,))
    po.close()
    po.join()
    print("(%s) End"%os.getpid())

    
运行结果:
(11484) start
writer启动(10184),父进程为(11484)
reader启动(10256),父进程为(11484)
reader从Queue获取到消息:y
reader从Queue获取到消息:o
reader从Queue获取到消息:n
reader从Queue获取到消息:g
reader从Queue获取到消息:G
reader从Queue获取到消息:e
(11484) End

以上是关于06.系统编程-1.进程的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程

什么是Java多线程编程?

多线程编程

使用 Pygments 检测代码片段的编程语言

以编程方式将按钮添加到片段

2016-06-27 并发编程