进程

  • 1、什么是进程
    进程指的就是一个正在运行的程序,或者说是程序的运行过程,即进程是一个抽象的概念
    进程是起源于操作系统的,是操作系统最核心的概念,操作系统所有其他的概念都是围绕进程展开的
    其中就有了多道技术的来由
    用进程就是为了实现并发

  • 操作系统(现代操作系统):
    操作系统是位于计算机硬件于软件之间的控制程序
    作用:
    1、将硬件的复杂操作封装成简单的接口,给用户或者应用程序使用
    2、将多个应用程序对硬件的竞争变的有序

  • 进程
    一个正在运行的程序,或者说是一个程序的运行过程

  • 串行、并发、并行
    串行:一个任务完完整运行完毕,才执行下一个
    并发:多个任务看起来是同时运行的,单核就可以实现并发
    并行:多个任务是真正意义上的同时运行,只有多核才能实现并行

  • 多道技术
    背景:想要再单核下实现并发(单核同一时刻只能执行一个任务(每起一个进程就会产生一把GIL全局解释器锁))
    并发实现的本质就:切换+保存状态
    多道技术:
    1、空间上的复用=》多个任务共用一个内存条,但占用内存是彼此隔离的,而且是物理层面隔离的
    2、时间上的复用=》多个任务共用同一个cpu
    切换:
    1、遇到io切换
    2、一个任务占用cpu时间过长,或者有另外一个优先级更高的任务抢走的cpu



开启进程的两种方式


 并发编程_第1张图片



并发编程_第2张图片



并发编程_第3张图片




  • 进程的方法与属性:

  • join:
    让父进程在原地等待,等到子进程运行完毕后(会触发wait功能,将子进程回收掉),才执行下一行代码

  • terminate:
    终止进程,应用程序给操作系统发送信号,让操作系统把这个子程序干掉 ,至于多久能干死,在于操作系统什么时候执行这个指令

  • is_alive: 
    查看子进程是否存在,存在返回True,否则返回False

  • os.getpid: 
    导入os模块,查看自己的门牌号

  • os.getppid: 
    导入os模块,查看父的门牌号

  • current_process().name: 
    导入from multiprocessing import Process,current_process
    查看子进程的名字
    主进程等子进程是因为主进程要给子进程收尸
    进程必须等待其内部所有线程都运行完毕才结束

    孤儿进程:

    在父进程被干掉的情况下会变成孤儿进程,无害,会被孤儿院((linux的孤儿院)init)回收

    僵尸进程:父进程没死,子进程死了,这时候的子进程就是僵尸进程

    正常情况下无害(会调用wait()方法进行回收操作), 父进程无限循环,且不被回收的情况下会无限制的生成子进程从而占用大量的操作系统资源
    当操作系统被大量僵尸进程占满内存后,操作系统就无法在启动其他的程序



并发编程_第4张图片




1、守护进程
守护进程其实就是一个“子进程”
守护进程会伴随主进程的代码运行完毕后而死掉
进程:
当父进程需要将一个任务并发出去执行,需要将该任务放到一个子进程里
守护:
当该子进程内的代码在父进程代码运行完毕后就没有存在的意义了,就应该
将该子进程设置为守护进程,会在父进程代码结束后死掉
实例:
from multiprocessing import Process
import time,os

def task(name):
        print('%s is running' %name)
        time.sleep(3)
        if __name__ == '__main__':
        p1=Process(target=task,args=('守护进程',))
        p2=Process(target=task,args=('正常的子进程',))

        p1.daemon = True # 一定要放到p.start()之前
        p1.start()
        p2.start()

        print('主')




  • 互斥锁

  • 互斥锁:可以将要执行任务的部分代码(只涉及到修改共享数据的代码)变成串行

    实例


并发编程_第5张图片


并发编程_第6张图片



并发编程_第7张图片



IPC机制:进程间通信,有两种实现方式

1、pipe:管道(前面已经说过)
2、queue:pipe+锁

并发编程_第8张图片并发编程_第9张图片





  • 线程

  • 什么是线程
    进程其实不是一个执行单位,进程是一个资源单位
    每个进程内自带一个线程,线程才是cpu上的执行单位

        如果把操作系统比喻为一座工厂
                在工厂内每造出一个车间===》启动一个进程
                每个车间内至少有一条流水线===》每个进程内至少有一个线程
    
        线程=》单指代码的执行过程(每个进程里都有一个线程)
        进程-》资源的申请与销毁的过程(向操作系统申请内存空间)



并发编程_第10张图片




并发编程_第11张图片




并发编程_第12张图片


守护线程:守护线程会在本进程内所有非守护的线程都死掉了才跟着死,即:守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)


并发编程_第13张图片



  • GIL全局解释器锁

  • 1 什么是GIL
    GIL本质就是一把互斥锁,那既然是互斥锁,原理都一样,都是让多个并发线程同一时间只能
    有一个执行
    即:有了GIL的存在,同一进程内的多个线程同一时刻只能有一个在运行,意味着在Cpython中
    一个进程下的多个线程无法实现并行===》意味着无法利用多核优势(运算)
    但不影响并发的实现

            GIL可以被比喻成执行权限,同一进程下的所以线程 要想执行都需要先抢执行权限
  • 2、为何要有GIL
    因为Cpython解释器自带垃圾回收机制不是线程安全的,也就是说如果没有GIL的情况下,在给一个值
    赋值的情况下如果被垃圾回收机制回收了,那就会出现错误

  • 3 有两种并发解决方案:
    多进程:计算密集型
    多线程:IO密集型(因为我们以后用的都是基于网络通信的套接字,而基于网络通信就存在大量IO,于是我们用的最多的都是多线程的方式)


同步:提交完任务后(用'串行'的方式运行)就在原地等待,直到任务运行完毕后拿到任务的返回值,再继续运行下一行代码


from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
import time
import random


def task(n):
    print('%s run...' % os.getpid())
    time.sleep(10)
    return n ** 2


def parse(res):
    print('...')


if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    # pool.submit(task,1)
    # pool.submit(task,2)
    # pool.submit(task,3)
    # pool.submit(task,4)

    l = []
    for i in range(1, 5):
        future = pool.submit(task, i)
        l.append(future)
        # print(future)
        # print(future.result())

    pool.shutdown(wait=True)  # shutdown关闭进程池的入口
    for future in l:
        # print(future.result())
        parse(future.result())

    print('主')


异步:提交完任务(绑定一个回调函数)后根本就不在原地等待,直接运行下一行代码,等到任务有返回值后会自动触发回调函数


用多进程实现异步:


# from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
# import os,time,random
#
# def task(n):
#         print('%s run...' %os.getpid())
#         time.sleep(5)
#         return n**2
#
# def parse(future):
#         time.sleep(1)
#         res=future.result()
#         print('%s 处理了 %s' %(os.getpid(),res))
#
# if __name__ == '__main__':
#         pool=ProcessPoolExecutor(4)
#
#         start=time.time()
#         for i in range(1,5):
#                 future=pool.submit(task,i)
#                 future.add_done_callback(parse) # parse会在futrue有返回值时立刻触发,
#                 # 并且将future当作参数传给parse,
#                 # 由主进程来回调函数,运行时间为  9.163417339324951
#         pool.shutdown(wait=True)
#         stop=time.time()
#         print('主',os.getpid(),(stop - start))




用多线程实现异步:


from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os
import time
import random


def task(n):
    print('%s run...' % current_thread().name)
    time.sleep(5)
    return n ** 2


def parse(future):
    time.sleep(1)
    res = future.result()
    print('%s 处理了 %s' % (current_thread().name, res))


if __name__ == '__main__':
    pool = ThreadPoolExecutor(4)
    start = time.time()
    for i in range(1, 5):
        future = pool.submit(task, i)
        future.add_done_callback(parse)  # parse会在futrue有返回值时立刻触发,
        # 并且将future当作参数传给parse
    pool.shutdown(wait=True)  # 哪个线程有时间了哪个线程就取回调函数,运行时间为 6.003463268280029
    stop = time.time()
    print('主', current_thread().name, (stop - start))




协程:只有遇到io才切换到其他任务的协程才能提升单线程的执行效率



  • 1、协程只有遇到io才切换到其他任务的协程才能提升
    单线程实现并发
    在应用程序里控制多个任务的切换+保存状态(协程只有在IO在单线程下切换到另外一个任务才能提升效率)
    优点:
    应用程序级别切换的速度要远远高于操作系统的切换
    缺点:
    多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地
    该线程内的其他的任务都不能执行了

                一旦引入协程,就需要检测单线程下所有的IO行为,
                实现遇到IO就切换,少一个都不行,因为一旦一个任务阻塞了,整个线程就阻塞了,
                其他的任务即便是可以计算,但是也无法运行了
  • 2、协程序的目的:
    想要在单线程下实现并发
    并发指的是多个任务看起来是同时运行的
    并发=切换+保存状态


实例: 协程 服务端: pip3 install gevent
from gevent import spawn,monkey;monkey.patch_all() # 相当于给下面的代码都打上标记,就都能识别

from socket import *
from threading import Thread


def talk(conn):
        while True:
            try:
                data = conn.recv(1024)
                if len(data) == 0: break
                conn.send(data.upper())
            except ConnectionResetError:
                break
        conn.close()


def server(ip, port, backlog=5):
    server = socket(AF_INET, SOCK_STREAM)
    server.bind((ip, port))
    server.listen(backlog)

    print('starting...')
    while True:
        conn, addr = server.accept()
        spawn(talk, conn, )  # 起一个协程,只要这个协程不死掉,进程也不会结束


if __name__ == '__main__':
    g = spawn(server, '127.0.0.1', 8080)  # 起一个进程
    g.join()  # 只要这个协程不死掉,进程也不会结束



客户端:
from threading import Thread,current_thread
from socket import *
import os


    def task():
        client = socket(AF_INET, SOCK_STREAM)
        client.connect(('127.0.0.1', 8080))

        while True:
            msg = '%s say hello' % current_thread().name
            client.send(msg.encode('utf-8'))
            data = client.recv(1024)
            print(data.decode('utf-8'))

if __name__ == '__main__':
    for i in range(500):
        t = Thread(target=task)
        t.start()