重修课程day30(进程)

Posted 方杰0410

tags:

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

一 进程的概念

 1 进程和程序的区别:程序就只是一段代码,在硬盘上的一堆文件,进程就时正在运行的程序的过程,进程是有优先级的过程。

 一个程序分别执行几次就会产生几个进程

  2进程一般是由程序,数据集和进程控制块。

                  2,1 程序:描述进程要完成哪些功能,以及如何完成。一个程序是多任务的,

                  2,2 数据集:程序在执行过程中所需要的使用资源

       2,3 进程控制块:用来记录进程的外部特征,描述进程的执行变化过程

  3 进程是程序的实体。

       4 进程是最小的资源管理。

二 并发,并行和串行

 什么是并发:看起来是多个程序一起执行,但其实是程序在内存中相互切换着执行

import multiprocessing
import time
def walk(n):
    print(\'>>>>>>start\',n)
    time.sleep(3)
    print(\'>>>>>>end\',n)
if __name__==\'__main__\':
    p1=multiprocessing.Process(target=walk,args=(2,))
    p2=multiprocessing.Process(target=walk,args=(5,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print(\'执行完成\')

 什么是并行:同时多个CPU同时执行多个程序

 什么是串行:一个程序执行完过后才能执行其他的程序,

import multiprocessing
import time
def walk(n):
    print(\'>>>>>>start\',n)
    time.sleep(3)
    print(\'>>>>>>end\',n)
if __name__==\'__main__\':
    p1=multiprocessing.Process(target=walk,args=(2,))
    p2=multiprocessing.Process(target=walk,args=(5,))
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    print(\'执行完成\')

如图:

 

三 阻塞和非阻塞

 阻塞:卡住,就是在执行一个程序的时候,需要从硬盘中寻找一些资料,在寻找资料的同时会将程序挂起,这个停止的过程就是阻塞。

 非阻塞:没有被卡住,一个程序启动,需要的资料已经全部读到了内存,不需要再去从硬盘中寻找,直接执行出了程序的结果,这就叫做非阻塞。

  阻塞和非阻塞针对的就是进程和线程,阻塞就是将程序挂起,非阻塞就是不会挂起当前的进程。

四 进程的状态

 一个进程有三种状态,分别是:进程态,阻塞态和就绪态

 

 1进程挂起是自身原因,遇到I/O阻塞,就会让出CPU让其他的程序执行,这样就不会浪费CPU的执行。

 2 与进程无关,就是在操作层面,某个程序占用的CPU时间过长,或者优先级的原因,调用CPU去执行其他的程序。

五 进程的创建

 1 操作系统的初始化

 2 一个进程在执行的过程中开启了堕饿鬼子进程,而进程是操作系统造的。

 3 在用户的交互请求,创建一个进程

 4 批处理初始化

无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。

六 multiprocessing模块  多进程模块

 Process:multiprocessing模块下的一个类,开启进程

 target:Process里面的一个方法,后面跟要开启的进程名

 args:后面是传入参数以元组的格式

 kwargs:传入参数,以字典的格式

 start:执行子程序

  基本格式:子进程=multiprocessing.Process(target=进程,args=(参数,))

  子进程.start()

# import multiprocessing
# import time
# def func(a):
#     print(111)
#     time.sleep(2)
#     print(222)
# if __name__==\'__main__\':
#     p=multiprocessing.Process(target=func,args=(1,))
#     p.start()
#     print(444)

 注意:主进程要等待子进程结束过后,才能结束,因为主进程要在子进程执行结束后关闭掉在这个程序。

  僵尸进程:在子进程没有执行完,而主进程已经挂掉了,而这些还没有执行完的子进程就是僵尸进程。

 在进程类里面默认起一个名(run)

# import multiprocessing
# import time
# class Func(multiprocessing.Process):
#     def run(self):
#         print(111)
#         time.sleep(3)
#         print(222)
# if __name__==\'__main__\':
#     p=Func()
#     p.start()
#     print(333)

 主进程的父id就是python

 主进程和父进程都有自己的内存空间。

join:等待子进程执行完过后再执行主进程。

termainate:干掉进程

is_alive:是否已干掉进程

name:进程名

pid:进程id

import multiprocessing
import time
import os
def func():
    print(111,os.getpid(),os.getppid())
    time.sleep(1)
if __name__==\'__main__\':
    p1=multiprocessing.Process(target=func)
    p2=multiprocessing.Process(target=func)
    p3=multiprocessing.Process(target=func)
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print(444,os.getpid(),os.getppid())

 七 socketserver模块

  socketserver:将进程和线程都封装好,并且还降低了IO。好处是代码简洁,坏处是耦合到了一块。

  BaseRequestHandler:socketserver模块下面的一个类。

  handle:是一个绑定方,只要绑定了对象,就可以触发他下面的功能

  ForkingTCPServer:windows系统上面不能使用,他是通过调用os.fork的功能开启了一个进程,二在windows系统上面没有这个功能,但是在linux系统上面就有这个功能,主要是进程的一种开启方式。

  ThreadingTCPServer:线程的一种开启方式。serue_forever:永远的运行,一直都可以连接。

  request:收发数据,相当于conn。

  streamRequestHandler:与tcp有关

  DatagramRequsetHandler:与udp有关

  socketserver:分为两类,一个是连接方面的,另一个是通信方面的。

 功能:只用于服务端,服务端利用多线程实现并发。使用步骤:在服务端创建一个类,而类下面必须要有一个handle方法。

如下:

import socketserver                                #步骤一 导入模块
class Myserver(socketserver.BaseRequestHandler):  #步骤二 自定义类,集成BaseRequestHandler类
    def handle(self):                              #步骤三 重写handle方法,需要实现的功能都在此方法下
        print(\'conn\',self.request)                  #self.requset 就是conn
        print(self.request.recv(1024))
if __name__ == \'__main__\':
    s=socketserver.ThreadingTCPServer((\'127.0.0.1\',8080),Myserver)     #步骤四,实例ThreadingTCPServer一个对象,参数是ip地址和端口,自定义的继承BaseRequestHandler类
    s.serve_forever()   

基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环

socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)

server类:

request类:

继承关系:

 

 

 

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer((\'127.0.0.1\',8080),FtpServer)
ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
  2. 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
  3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
  4. 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

源码分析总结:

基于tcp的socketserver我们自己定义的类中的

  1.   self.server即套接字对象
  2.   self.request即一个链接
  3.   self.client_address即客户端地址

基于udp的socketserver我们自己定义的类中的

  1.   self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b\'adsf\', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=(\'127.0.0.1\', 8080)>)
  2.   self.client_address即客户端地址

 详情:http://www.cnblogs.com/kellyseeme/p/5525023.html

    http://www.cnblogs.com/linhaifeng/articles/6129246.html#_label14

八 同步和异步

 什么是同步:一个程序在执行某个程序时,发生某个请求,需要一定时间返回消息,而这个程序就会等待消息返回,才继续执行下去。

 什么是异步:一个程序在执行某个程序时,发生某个请求,需要一定时间返回消息,进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率、

九 GIL(全局解释器锁)

    3,1 ,1  Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
     3,1,2  GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL
     3,1,3  GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作

    3,2,1  GIL的早期设计

Python支持多线程,而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像mysql这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,并且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

    3,3,1  GIL的影响

无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

计算密集型:

#coding:utf8
from threading import Thread
import time

def counter():
    i = 0
    for _ in range(50000000):
        i = i + 1

    return True


def main():

    l=[]
    start_time = time.time()

    for i in range(2):

        t = Thread(target=counter)
        t.start()
        l.append(t)
        t.join()

    # for t in l:
    #     t.join()

    end_time = time.time()
    print("Total time: {}".format(end_time - start_time))

if __name__ == \'__main__\':
    main()


\'\'\'
py2.7:
     串行:25.4523348808s
     并发:31.4084379673s
py3.5:
     串行:8.62115597724914s
     并发:8.99609899520874s

\'\'\'
View Code

3,4,1解决方案

用multiprocessing替代Thread multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

#coding:utf8
from multiprocessing import Process
import time

def counter():
    i = 0
    for _ in range(40000000):
        i = i + 1

    return True

def main():

    l=[]
    start_time = time.time()

    for _ in range(2):
        t=Process(target=counter)
        t.start()
        l.append(t)
        #t.join()

    for t in l:
       t.join()

    end_time = time.time()
    print("Total time: {}".format(end_time - start_time))

if __name__ == \'__main__\':
    main()


\'\'\'

py2.7:
     串行:6.1565990448 s
     并行:3.1639978885 s

py3.5:
     串行:6.556925058364868 s
     并发:3.5378448963165283 s

\'\'\'
View Code

当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

总结:因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能 - 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现 - GIL在较长一段时间内将会继续存在,但是会不断对其进行改进。

   

以上是关于重修课程day30(进程)的主要内容,如果未能解决你的问题,请参考以下文章

重修课程day32(网络编程六之进程三)

重修课程day35(网络编程九之协程)

重修课程day34(网络编程八之线程二)

重修课程day9(函数之有参函数)

重修课程day22(面向对象三之继承和派生)

重修课程day26(面向对象6之反射,内置函数和常用模块)