zoukankan      html  css  js  c++  java
  • 线程、进程、协程、同步、异步、阻塞、非阻塞

    一、计算机存储

    RAM:读写存储器,例如计算机内存、手机内存。掉电不保存,读写速度高

    ROM:只读存储器,例如计算机硬盘、手机SD卡。掉电依然保存,读速度低

    二、计算机CPU

    电脑的CPU,手机的处理器。CPU分为单核和双核或多核,单核处理器在微观上只能在同一时刻处理一项任务,例如在电脑上用酷狗听歌,那我们此时也可以同时打开网页浏览新闻,酷狗听歌和网页几乎可以同时操作,这是为什么呢?因为我们的CPU不可能一直处理一件事,假如电脑的任务有一个清单那么长的话,CPU会从第一个开始执行一会儿再运行一会儿第二个任务,之后再处理第三个第四个,只是它处理速度非常快,我们在感官上会觉得电脑在同一时刻可以处理很多事。这里的很多事也可以说很多个进程。

    三、进程、线程、协程

    在电脑上打开酷狗音乐应用,酷狗音乐比作一个进程,在酷狗音乐里同时下载10首歌曲,这10首歌曲相当于线程。

    进程:动态概念,运行的状态。相对独立,系统给进程单独开辟内存空间,数据安全,较完善的进程间通信。

               缺点是需要消耗系统资源非常大,占用CPU和 内存,PCB进程控制块专门用来存储进程信息。

    线程:是进程的衍生品,一个进程可以包含多个线程。消耗的资源是进程的1/20,依赖于进程而存在。它可以被抢占(中断)和临时挂起(睡眠),这种做法叫做让步。

               线程一般是以并发方式执行的。在单核CPU系统中,真正的并发是不可能的,所以线程间每个线程运行一小会儿,然后让步给其他线程

               缺点是线程之间是共享进程的资源的,不如进程间传输的数据安全。

    协程:在一个进程中,只有一个线程,这个线程一会儿执行A任务,一会儿执行B任务

    进程池:原理:进程消耗的资源较大,假如有1000个任务,需要1000个进程,创建1000个进程,销毁1000个进程,对资源的消耗很庞大。

                但是如果把进程放在池子里,假如计算机内核是4核,能同时执行4个任务,那么我创建10个进程放在进程池里,挂起这10个进程,1000个任务随机10个进入进程池,自然会有10个进程去处理这10个事件,10个事件处理完之                  后,进程池里的10个进程不销毁,仍然再跑着,等着另外10个再跑进来,如果不到10个任务那就闲置几个进程。避免了进程的频繁创建和销毁。就像10个服务员始终待命一样。有事儿就让他们做。

                进程与进程间是独立的,例如登录一个APP需要用微信登录,或微博登录,那这个APP是个单独的进程,微信也是单独的进程,两个进程之间需要通信才能完成登录的操作。

    四、计算机IO

    IO代表input输入output输出,分为IO设备和IO接口,

    IO设备包含存储设备,磁盘、光盘、硬盘等,键盘、监视器、打印机等。

    IO接口指单片机与外设的IO接口芯片。

    IO端口指的是IO接口电路中带有端口地址的寄存器或缓冲器,IO设备的单片机通过端口地址就可以对端口中的信息进行读写,传数据的端口叫做数据口,传命令的端口叫命令口,传状态的端口叫做状态口。一个IO接口上可以有多个IO端口

    IO请求:

    阻塞IO:资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或者超时)

    非阻塞IO:资源不可用时,IO请求离开,返回数据标识资源不可用

    同步IO:应用阻塞在发送或接受数据的状态,直到数据成功传输或返回失败。理解:提交请求-->等待服务器处理-->处理完毕返回   这个期间客户端不能干任何事(比如B/S模式)

    异步IO:应用发送或接收数据后立刻返回,数据写入系统缓存,用系统完成数据发送或接收,并返回成功或失败的信息给应用。理解:请求通过事件触发-->服务器处理(这时客户端仍然可以做其他事情)-->处理完毕(比如AJAX异步技术)

    多进程、多线程优点:并行执行多任务,为了提高执行效率,处理速度更快一些。

    多进程、多线程缺点:占用系统资源

    进程:动态概念,运行的状态。相对独立,系统给进程单独开辟内存空间,数据安全,较完善的进程间通信。

            缺点:需要消耗系统资源非常大,占用CPU和 内存,PCB进程控制块专门用来存储进程信息。

    线程:是进程的衍生品,一个进程可以包含多个线程。消耗的资源是进程的1/20,依赖于进程而存在。

            缺点:线程之间是共享进程的资源的,不如进程间传输的数据安全

    程序:程序是静态的,程序执行的过程是动态的,从开始到结束的过程称为程序的生命周期,是一个动态的概念。

    进程的

    os模块操作底层系统相关。应用层调用内核程序来创建多线程再来反馈给应用层

    新建进程:

    1.fork()方法

    #只在POSIX系统上可用,windows版没有

    import os
    
    pid=os.fork()   #复制了一个进程,现在有两个进程了,主进程(pid默认为大于0的)和子进程(pid等于0)
    
    if pid==0:     #子进程执行
    
       xxx
    
    elif pid>0:     #主进程执行
    
       xxx

    #程序本身是个主进程,主进程和子进程谁先开始执行不一定,感官上是同时执行,微观上可能执行顺序不是同一时刻

    进程有几种状态:

    1.就绪:准备要执行,CPU的时间片还没有轮到他

    2.运行态:运行状态

    3.等待态:遇到阻塞了,等待某种条件的触发

    4.停止态:停止了,但是没有被销毁

    5.僵尸态:进程结束,父进程没有对子进程进行收尸处理,占用资源,损害内存

    2.multiprocessing类

    更方便的管理

    import multiprocessing  #标准库模块
    
    import os
    
    p=multiprocessing.Process(target=worker,name="myprocess",args=(2,5))    #创建子进程
    
                                 #target参数为一个函数名,启动进程的时候,执行哪个函数
    
                                 #name参数为 给进程起个名字,不起名字的时候,默认为process1 ,2,3
    
                                #args参数为  第target函数的参数
    
                                #p表示进程对象
    
    jobs=[ ]
    
    jobs.append(p) #把子进程加入列表中是为了方便父进程对他回收
    
    p.start()   #启动子进程
    
    for i in jobs:   
    
        i.join()  #回收子进程,join()是个阻塞函数,只有子进程执行完,才能冲破这个阻塞
    
    print os.getpid()   #进程的编号就是getpid,获取当前进程的pid

    子进程如果要修改全局变量,在子进程结束后这个全局变量恢复原来的值。

    进程池:

    原理:进程消耗的资源较大,假如有1000个任务,需要1000个进程,创建1000个进程,销毁1000个进程,对资源的消耗很庞大。

    但是如果把进程放在池子里,假如计算机内核是4核,能同时执行4个任务,那么我创建10个进程放在进程池里,挂起这10个进程,1000个任务随机10个进入进程池,自然会有10个进程去处理这10个事件,10个事件处理完之后,进程池里的10个进程不销毁,仍然再跑着,等着另外10个再跑进来,如果不到10个任务那就闲置几个进程。避免了进程的频繁创建和销毁。就像10个服务员始终待命一样。有事儿就让他们做。

    import multiprocessing
    
    p=multiprocessing.Pool(processes=4)  #进程池里有4个进程,p是进程池对象,而不是进程对象
    
    result=[]
    
    for i in range(10)    #一共有10个事件
    
         msg="hello"
    
         result.append(p.apply_async(worker,(msg,)))    #apply_async异步处理 ,先把4个事件添加到进程池里,另外6个处于等待状态,worker为处理函数,(msg,)为worker函数参数
    
                                                        #apply是逐个处理,每次只给进程池添加一个事件,一个一个执行属于阻塞函数
    
                                                        #result为事件对象
    
    for res in result:  #取出事件对象
    
          print res.get()    #得到事件返回值
    
    p.close()    #阻塞函数,把进程池关闭掉,不让新的任务添加了
    
    p.join()    #阻塞函数,回收进程池

    进程间通信:

    进程与进程间是独立的,例如登录一个APP需要用微信登录,或微博登录,那这个APP是个单独的进程,微信也是单独的进程,两个进程之间需要通信才能完成登录的操作。

    IPC是一组编程接口,通信方式。包括:明管道、共享内存、消息队列、信号灯、信号、socket

    进程间用全局变量是不可行的,因为子进程即使对全局变量做了更改,父进程也收不到这个更改,子进程的内容和父进程的内容完全是分隔的。是两块不同的内存

    管道通信:

    from multiprocessing import Process,Pipe
    
    process_list=[]
    
    parent_conn,child_conn=Pipe()  #管道就是在系统内存中开辟的信道,双向管道,父进程和子进程都可见
    
    def f(name):
    
        child_conn.send(["111"+str(name)])    #往管道里发送值
    
        print os.getppid() ,os.getpid()       #打印父进程的进程号和子进程的进程号
    
    for i in range(10):
    
         p=Process(target=f,args=(i,))     #f是个函数名
    
         p.start()
    
         process_list.append(p)
    
    for j in process_list:
    
         j.join()  
    
    for p in range(10):
    
         print parent_conn.recv()     #父进程收到那10个进程往管道里发送的值并打印出来

    信号:

    代表某种含义的信号,而不是传输字符串,信号可以用数字符号表示也可以用大写英文字符表示

    在linux中输入kill -l列出的信号列表

    linux下常见信号例如:

    SIGINT        通常在ctrl+c时发出     默认操作-终止

    import os

    os.kill(pid,sig)     #pid进程编号,sig为数字符号或者大写英文字符

     import signal 

    signal.signal(signal.SIGINT,myHandler)  #处理信号,不是阻塞函数,异步

    #第1个参数是要处理的信号,只有接收到这个信号的时候才会处理,接收到别的信号不会处理。

    #第2个参数是处理的方式,有三种可能,1)SIG_DFL默认方式处理;2)SIG_IGN忽略处理;3)function传函数,这个函数只能有两个参数,第一个参数是接收到的信号,第二个是信号类型

    signal.pause()    #等待信号,阻塞函数

    终端就是一个进程,按ctrl+c就是一个进程给另一个进程发消息

    消息队列:

    在内存中分配空间,一个消息一个消息的来放,一个消息一个消息的来取,以个体为单位来使用。

    from multiprocessing import Process,Queue
    
    q=Queue()  #创建消息队列
    
    def f(name):
    
          time.sleep(1)   #每个一秒放一个字符串
    
          q.put(['hello'+str(name)])     #每个进程可以放字符串,也可以放一个列表
    
     for i in range(10):
    
           p=Process(target=f,args=(i,))
    
           p.start()
    
           process_list.append(p)
    
    for j in process_list:
    
           j.join()
    
    for i in range(10):
    
           print q.get()

    pool=Pool(5)

    pool.map(函数名字,函数的参数列表)  等价于  result.append(p.apply_async(run,(i,)))

    map(函数名字,函数的参数列表)   内建函数

    线程:

    像线程一样管理进程

    thread模块(Python3中已经不用)和threading模块(功能更强大)

    threading模块对象

    Thread类

    Lock类                    同步与互斥处理

    RLock类                  同步与互斥

    Condition类             同步与互斥

    Event类                   同步与互斥

    Semaphore类          同步与互斥

    BoundedSemaphore类

    Timer类

    import threading
    
    threads=[]
    
    t1=threading.Thread(target=music,args=('baby',)) #创建一个线程
    
    t2=threading.Thread(target=move,args=('afraid',))   #创建第二个线程
    threads.append(t1)   
    
    threads.append(t2)
    
    for t in threads:
    
        t.setDaemon(False)  #默认为false,主线程执行完不退出,为true时,主线程执行完退出
    
         t.start()    #启动线程,线程1执行music函数,线程2执行move函数
    
    for t in threads:
    
         t.join()   #线程的回收
    
       
    
    #当我有单个线程的时候,这个叫线程或者进程是没啥区别的
    
    #当我创建两个线程的时候,原来的程序的线程叫做主线程,另外两个是分支线程,现在一共有三个线程
    
    #线程和进程的区别就是,主程序中有个global变量,子进程对global变量进行修改后不影响原来的值,改变只在当前子进程中有效,其他进程读取global变量无影响;
    
    #但是分支线程修改全局变量后影响其他线程对global的取值,对其他线程有影响,此时global变量叫做争夺变量

    1.Event事件

    import threading
    
    from time import sleep,ctime
    
    e=threading.Event()   #创建一个事件,对所有线程都可见
    
    t1=threading.Thread(name='block',target=wait_for_event,args=(e,)  )  #创建线程
    
    ti.start()
    
    t2=threading.Thread(name='nonblock',target=wait_for_timeout,args=(e,3))
    
    t2.start()
    
    sleep(4)
    
    e.set()
    
    def wait_for_event(e):
    
          print e.wait()  #阻塞函数,直到执行e.set()  ,才执行这条语句,返回值为布尔类型
    
    def wait_for_event_timeout(e,t):
    
           while not e.isSet():  #判断这个事件是否被设置
    
                    print e.wait(t) 
    
     #e.wait(t)阻塞函数,等待e.set()的执行,如果超过t秒就不等了,也就是最多等t秒,然后执行这条语句,返回布尔类型值,如果是因为到达t秒了才执行就会返回false,如果是因为等到e.set()了返回值就会true

    2.Lock锁

    import threading
    
    from time import sleep
    
    a=b=0
    
    lock=threading.Lock()    #创建锁对象
    
    t=threading.Thread(target=value)   #创建线程
    
    t.start()    #线程执行
    
    while True:
    
         a+=1
    
         b+=1
    
    def value():
    
         while True:
    
                if a!=b:
    
                     print a,b   #因为线程之间对全局变量的数据是相互影响,所以a和b的值可能不会相等
    
    ---------方式二
    
    while True:
    
         lock.acquire()   #在此处加锁
    
         a+=1
    
         b+=1
    
         lock.release()   #直到解锁结束,第二个锁才会冲破阻塞
    
    def value():
    
         while True:
    
                lock.acquire()   #这块再加锁时,程序变阻塞,跟上面那个lock.acquire()谁先执行不一定,反正执行第二个acquire()时会变阻塞
    
                if a!=b:
    
                     print a,b          #这样的话,a和b一定是相等的了
    
                 lock.release()  
    
    #意思就是value函数和主程序中的while true循环只能有一个地方在执行

    JIL:

    IO密集型适合使用多进程,使用多核

    协程:可以用来处理多线程IO高并发的处理方法

    计算密集型:使用多进程

    IO密集型:使用多线程

    如果计算密集和IO密集都有,使用进程+协程(单线程)

    协程的实现方式:在处理很多事件的时候,可能有一个事件处理一半就开始阻塞了,那么它就会跳出来执行其他事件,等这个阻塞被冲破了它再回过头来执行这个事件,这样就大大节省了处理时间

    yield关键字只能放在函数当中,这样的函数就变成一个生成器,作用就是在函数执行过程中跳出来

    协程就是单线程或单进程

    1.Greenlet协程:

    安装pip greenlet

    from greenlet import greenlet
    
    gr1=greenlet(test1)  #注册协程1,test1为函数名称,把函数当成注册对象
    
    gr2=greenlet(test2)  #注册协程2
    
    gr1.switch()  #启动选择器,选择启动gr1这个协程,开始执行test1函数
    
    def test1():
    
         print "12"   #第一步打印12
    
         gr2.switch()   #记录函数栈帧,切换到gr2协程上,开始执行test2函数
    
         print "34"    #第三步打印34
    
    def test2():
    
         print "56"    #第二步打印56
    
         gr1.switch()   #切换到gr2上,开始执行test1函数
    
         print "78"

    2.gevent类

    import gevent

    from time import sleep

    def foo():

        print "x"

        gevent.sleep(2)  #IO事件的阻塞,sleep(2) 不是IO阻塞,是程序阻塞

        print "y"

    def bar():

         print 'a'

         gevent.sleep(1)

         print 1

    def func3():

         print 'b'

         gevent.sleep(0)

         print 'c'

    l=[gevent.spawn(foo),gevent.spawn(bar),gevent.spawn(func3)]  #注册3个协程对象

    gevent.joinall(l)   #执行协程对象,参数必须是列表

  • 相关阅读:
    ES-- Elasticsearch粗略分析
    springMVC之@Request
    Spring Boot入门
    反射四(动态代理)
    反射三(泛型)
    反射二(字段)
    反射一(方法)
    nutch和solr建立搜索引擎基础(单机版)
    Cinnamon桌面是怎么回事儿
    开启属于你的GNOME桌面
  • 原文地址:https://www.cnblogs.com/zz27zz/p/8690985.html
Copyright © 2011-2022 走看看