美文网首页
并发编程:操作系统,进程,multiprocessing模块

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

作者: 牛奶加醋 | 来源:发表于2018-08-07 16:39 被阅读7次

操作系统介绍

  • 程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,只需要考虑自己的应用软件的编写就可以了,应用软件直接使用操作系统提供的功能来间接使用硬件。
  • 操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。
    • 隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清>晰的模型(系统调用接口)。
    • 将应用程序对硬件资源的竞态请求变得有序化

操作系统发展史

  • 第一代计算机(1940~1955):真空管和穿孔卡片

  • 第二代计算机(1955~1965):晶体管和批处理系统

  • 第三代计算机(1965~1980):集成电路芯片和多道程序设计

    • 多道技术:

      多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。

      • 空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。
      • 时间上的复用:当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%
  • 第四代计算机(1980~至今):个人计算机

操作系统总结

  • 即使可以利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。
一 操作系统的作用:
    1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

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

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

进程

  • 进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。
  • 进程与程序的区别
    • 程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。

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

  • 并发与并行

    • 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发
    • 并行:同时运行,只有具备多个cpu才能实现并行
  • 进程的创建

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

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

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

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

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

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

    • 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
    • 出错退出(自愿,python a.py中a.py不存在)
    • 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)
    • 被其他进程杀死(非自愿,如kill -9)
  • 进程的层次结构

    • 无论UNIX还是windows,进程只有一个父进程,不同的是:
      1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
      2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
  • 进程的状态

进程的三种状态.png
  • 进程并发的实现

    • 进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)


      进程状态表.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、思考每来一个客户端,服务端就开启一个新的进程来服务它,这种实现方式有无问题?

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

僵尸进程和孤儿进程

  • 僵尸进程是子进程结束后,留下的一些状态信息,以供父进程随时查看这些子进程的状态,如果父进程一直运行,会产生很多僵尸进程,导致pid被占用
  • 孤儿进程是子进程还未结束的情况下,父进程已经结束,在linux中由init进程来负责回收,是无害的.

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、判断上述三种效果,哪种属于并发,哪种属于串行?

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

相关文章

网友评论

      本文标题:并发编程:操作系统,进程,multiprocessing模块

      本文链接:https://www.haomeiwen.com/subject/pueavftx.html