并发编程:操作系统,进程,multiprocessing模块

2018-08-07  本文已影响7人  牛奶加醋

操作系统介绍

操作系统发展史

操作系统总结

一 操作系统的作用:
    1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

二 多道技术:
    1.产生背景:针对单核,实现并发
    ps:
    现在的主机一般是多核,那么每个核都会利用多道技术
    有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
    cpu中的任意一个,具体由操作系统调度算法决定。

    2.空间上的复用:如内存中同时有多道程序
    3.时间上的复用:复用一个cpu的时间片
       强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行

进程

需要强调的是:同一个程序执行两次,那也是两个进程

新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:

在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

关于创建的子进程,UNIX和windows

1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
进程的三种状态.png

multiprocessing模块

multiprocessing模块介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。

Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

Process类的介绍

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,可用来开启一个子进程

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:

group参数未使用,值始终为None

target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'egon',)

kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}

name为子进程的名称

方法介绍:

p.start():启动进程,并调用该子进程中的p.run() 
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  

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

p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间。

属性介绍:

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

p.name:进程的名称

p.pid:进程的pid

Process类的使用

注意:在windows中Process()必须放到# if name == 'main':下

from multiprocessing import Process
import time

# 开启进程方式一
# def task(name):
#     print(f"{name} is running...")
#     time.sleep(2)
#     print(f"{name} is done...")

# if __name__ == '__main__':
#     p = Process(target=task, args=("子进程1",))
#     p.start()
#     print("主进程结束....")


# 开启进程方式二

class Myprocess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f"{self.name} is running...")
        time.sleep(2)
        print(f"{self.name} is done...")

if __name__ == '__main__':
    p = Myprocess("子进程1")
    p.start()
    print("主进程结束....")

os.getpid() 查看当前进程的进程ID号

os.getppid()查看当前进程的父进程ID号

p.pid 也可以查看当前p进程的pid

练习

1、思考开启进程的方式一和方式二各开启了几个进程?

各开启了5各进程,1个父进程,4个子进程

2、进程之间的内存空间是共享的还是隔离的?下述代码的执行结果是什么?

from multiprocessing import Process

n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了

def work():
    global n
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)

进程和进程之间内存空间是隔离的,运行结果为

主进程内 100

子进程内 0

3、基于多进程实现并发的套接字通信?

server

from multiprocessing import Process
import socket

IP_PORT = ('127.0.0.1', 8080)


def server1(IP_PORT):
    server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(IP_PORT)
    server.listen(5)

    while True:
        conn, client_addr = server.accept()
        p = Process(target=talk, args=(conn,))
        p.start()
        
    server1.closer()
        

def talk(conn):
    while True:
        try:
            msg = conn.recv(1024)
            if not msg:
                continue
        except ConnectionResetError as e:
            print(e)
            break
            
        conn.send(msg.upper())


if __name__ == '__main__':
    server1(IP_PORT)

    

客户端

import socket

IP_PORT = ('127.0.0.1', 8080)

client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

client.connect(IP_PORT)

while True:
    msg = input('>>>:').strip()
    if not msg:
        continue
    client.send(msg.encode('utf-8'))
    data_recv = client.recv(1024)
    print(data_recv.decode('utf-8'))

4、思考每来一个客户端,服务端就开启一个新的进程来服务它,这种实现方式有无问题?

客户端无法正常结束,导致占用系统资源无法释放....

僵尸进程和孤儿进程

Process对象的join方法

在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况

情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。

情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用

from multiprocessing import Process
import time
import random
import os

def task():
    print('%s is piaoing' %os.getpid())
    time.sleep(random.randrange(1,3))
    print('%s is piao end' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    p.join() #等待p停止,才执行下一行代码
    print('主')

Process对象的其他属性或方法

进程对象的其他方法一:terminate与is_alive

from multiprocessing import Process
import time
import random

def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s is piao end' %name)

if __name__ == '__main__':
    p1=Process(target=task,args=('egon',))
    p1.start()

    p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
    print(p1.is_alive()) #结果为True

    print('主')
    print(p1.is_alive()) #结果为False

进程对象的其他属性:name与pid

from multiprocessing import Process
import time
import random

def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s is piao end' %name)

if __name__ == '__main__':
    p1=Process(target=task,args=('egon',),name='子进程1') #可以用关键参数来指定进程名
    p1.start()

    print(p1.name,p1.pid,)

三 练习题

1、改写下列程序,分别别实现下述打印效果

from multiprocessing import Process
import time
import random

def task(n):
    time.sleep(random.randint(1,3))
    print('-------->%s' %n)

if __name__ == '__main__':
    p1=Process(target=task,args=(1,))
    p2=Process(target=task,args=(2,))
    p3=Process(target=task,args=(3,))

    p1.start()
    p2.start()
    p3.start()

    print('-------->4')

效果一:保证最先输出-------->4

-------->4
-------->1
-------->3
-------->2

# 直接运行

效果二:保证最后输出-------->4

-------->2
-------->3
-------->1
-------->4

# 在print('-------->4') 之前添加
    p1.join()
    p2.join()
    p3.join()

效果三:保证按顺序输出

-------->1
-------->2
-------->3
-------->4

# 在p1.start()下面添加p1.join()
# 在p2.start()下面添加p2.join()
# 在p3.start()下面添加p3.join()

2、判断上述三种效果,哪种属于并发,哪种属于串行?

效果一,二属于并发,效果三输入串行.

上一篇 下一篇

猜你喜欢

热点阅读