• 什么是多进程-多线程-多协程 ----进程和多线程


    进程通信

    • 进程之间都是相互独立的
    • 进程就是正在运行的程序,计算机进行资源(计算资源和存储资源)分配的最小单位
    • 所有的进程都是由另外一个进程创建的,创建进程是父进程,被创建进程就是子进程
    • linux中开机会启动两个进程
      • systemd --》PID:1 PPID:0 --》用来启动用户进程
      • kthreadd -->kernel thread daemon内核线程的守护进程 PID:2 PPID:0 --》用来启动内核进程

    进程的组成

    ​ PCB(是进程的唯一标识)+数据段+代码段

    PCB:为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。

    进程之间的通信方式

    管道

    ​ 管道又分为匿名管道命名管道

    匿名管道
    命名管道
    信号

    信号的本质其实就是修改程序的PCB

    PCB:为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。

    信号量
    • 信号量一般和共享内存一起搭配只用
    • 是一种锁,可用规定同一时刻有几个程序可用访问这个共享内存
    共享内存

    ​ 最快通信方式

    socket

    ​ 一般不同主机上的不同进程通信

    消息队列
    • 支持的传输类型多一些
    • 内核启动就创建好了的
    • 存储空间比较小

    进程线程

    系统知识

    计算机系统抽象组成: CPU + 存储器 + IO

    资源:

    1. 计算资源:cpu
    2. 存储资源:内存、磁盘等
    cpu时间片(抽象概念)

    对于单核cpu同一时刻只能有一个任务运行。

    1. 并发:交替执行(某时间段内的处理能力)
    2. 并行:同时执行

    img

    img

    线程:
    • 线程是操作系统最小的调度单位, 是一串指令的集合
    • 运行在进程之上的
    • 操作系统进行调度的最小单位
    进程:

    ​ 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度 的基本单位

    进程与线程

    • 真正在cpu上运行的是线程
    • 线程共享内存空间;不同进程的内存是独立的
    • 一个线程只能属于一个进程,而一个进程可以有多个线程, 但至少有一个线程
    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。进程的资源是独立的
    • 同一个进程的线程之间可以直接交流,一个线程可以控制和操作同一个进程里的其它线程,进程只能操作子进程;两个进程想通信,必须通过一个中间代理来实现
    • 一个主线程改变可能会影响其它线程,改变父进程不会影响子进程

    img

    多任务操作系统工作模式

    • 多进程模式
    • 多线程模式
    • 多进程+多线程模式

    进程状态模型

    img

    img

    进程与线程

    img

    linux进程的五种状态

    Linux进程简介及其五种状态
    img

    img

    停止状态按ctrl+z停止的话,按fg可以继续运行

    load average

    就绪+运行状态 线程队列的一个情况来反映出cpu一段时间内的繁忙程度

    1核cpu :load average = 1 处于满负荷的临界点了>1就超负荷了

    threading

    线程

    • 线程被称为轻量级进程(Lightweight Process,LWP),是cpu调度的基本单位
    • 组成:线程ID、当前指令指针(PC)、寄存器集合、堆栈组成
    • 在单个程序中同时运行多个线程完成不同的工作,称为多线程

    功能

    ​ threading用于提供线程相关的操作,线程是应用程序中工作的最小单元、

    threading模块提供的常用类

    • Thread:创建线程
    • Lock/RLock:互斥锁

    threading——Thread

    Thread构造方法

    构造方法: Thread(group=None, target=None, name=None, args=(), kwargs={})

    • group: 线程组,目前还没有实现,库引用中提示必须是None;
    • target: 要执行的方法;
    • name: 线程名;
    • args/kwargs: 要传入方法的参数。

    Thread实例方法

    • t.name:获取或设置线程的名称
    • t.getName()/setName(name): 获取/设置线程名。
    • t.is_alive()、t.isAlive():判断线程是否为激活状态。返回线程是否在运行。正在运行指启动后、终 止前。
    • t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才 有效,否则它只返回None
    • t.run() :线程被cpu调度后自动执行线程对象的run方法
    • t.start(): 线程准备就绪,等待CPU调度,start会自动调用t.run()
    • t.join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout (可选参数)。
    • t.setDaemon(bool): 设置是后台线程(默认前台线程(False))。(在start之前设置)
    • 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论 成功与否,主线程和后台线程均停止
    • 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程 也执行完成后,程序停止
    • **t.isDaemon:**判断是否为后台线程
    import requests
    import time
    import functools
    import threading
    
    def runtime(func):
        print("this is runtime")
        #保留传递进来的函数的元数据,将他的元数据赋值给Inner
        @functools.wraps(func)
        def inner(*args,**kwargs):
            start = time.time()
            result = func(*args,**kwargs)
            end = time.time()
            print(f"函数执行花了{end - start}s")
            return result
        return inner
    def get_content(url):
        text =requests.get(url).content
        time.sleep(0.5)
        print("get content")
    
    @runtime
    def main():
        t_list = []
        for i in range(5):
            #get_content("https://www.baidu.com")   单线程
            # 创建线程
            #target -->指定传入的方法核名字,要做什么
            #args -->指定方法需要传入的参数,元组类型
            t = threading.Thread(target = get_content,args=("https://www.baidu.com",))
            t_list.append(t)
            # t.daemon = True    #默认是前台线程(False)设置后台线程,主线程退出子线程也退出
            t.start()    #启动线程 自动执行run方法
        for t in t_list:    #主线程执行这一部分
        #     #阻塞当前环境上下文,直到t的线程执行完成
        #     #谁执行这个join代码,谁就是当前环境
            t.join(timeout=0.2)
        print("ending .....")
    main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    使用自定义类创建

    import threading
    class MyThread(threading.Thread):
        def __init__(self,num):
            super(MyThread,self).__init__()
            self.num = num
        def run(self):
            print(f"running on numers:{self.num}")
    
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    threading——Lock

    在多线程中使用lock可以让多个线程在共享资源的时候遵循一定的规则。

    常见锁类型

    • Lock()/RLock:普通锁(互斥锁) 解决资源争用,数据读取不一致等
    • Semaphore :信号量 最多允许同时N个线程执行内容
    • Event: 事件锁 根据状态位,决定是否通过事件
    • Condition: 条件锁
    Lock()/RLock:普通锁(互斥锁)

    解决资源争用,数据读取不一致等

    构造方法:Lock()

    实例方法:

    • acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。
    • release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。
    # #LOCK / RLOCK
    # #互斥锁  --  解决资源争用,数据读取不一致等问题
    #
    # import threading
    # import time
    # import functools
    #
    # def runtime(func):
    #     print("this is runtime")
    #     #保留传递进来的函数的元数据,将他的元数据赋值给Inner
    #     @functools.wraps(func)
    #     def inner(*args,**kwargs):
    #         start = time.time()
    #         result = func(*args,**kwargs)
    #         end = time.time()
    #         print(f"函数执行花了{end - start}s")
    #         return result
    #     return inner
    #
    # num = 0
    # @runtime
    # def sum_num(i):
    #     lock.acquire()  #获取锁
    #     global num
    #     time.sleep(0.5)
    #     num +=i
    #     print(num)
    #     lock.release() #释放锁
    #
    # lock = threading.Lock() #创建一个锁对象,一般只在公共资源加锁
    # for i in range(5):
    #     t = threading.Thread(target = sum_num,args=(i,))
    #     t.start()
    
    # import threading
    # import time
    #
    # num = 0
    # def sum_num(i):
    #     # lock.acquire()  #获取锁
    #     with lock:   #类似withopen,自动获取核释放锁
    #         global num
    #         time.sleep(0.5)
    #         num +=i
    #         print(num)
    #     # lock.release() #释放锁
    #
    # lock = threading.RLock() #创建一个锁对象,一般只在公共资源加锁
    # for i in range(5):
    #     t = threading.Thread(target = sum_num,args=(i,))
    #     t.start()
    
    #死锁
    #1.第一种情况
    #LOCK 原始锁 获取锁之前不做判断,直到获取到锁位置
    #RLOCK 重入锁 获取锁之前先判断,如果自己有了锁就立即返回
    
    # import threading
    # # lock1 = threading.Lock()
    # lock1 = threading.RLock()
    # lock1.acquire()
    # print("lock1 acquire")
    # lock1.acquire()
    # print("lock1 acquire2")
    # lock1.release()
    # print("lock1 release")
    # lock1.release()
    # print("lock1 releas2")
    
    # 第二种
    #账户类
    from threading import Lock,Thread
    class Account:
        def __init__(self,_id,balance,lock):#每个账户自带一个锁,只要对balance
            self._id = _id
            self.balance = balance
            self.lock = lock
    
        def withdraw(self,amount):
            self.balance -= amount
    
        def deposite(self,amount):
            self.balance += amount
    
        def get_balance(self):
            return self.balance
    
    #生成两个账户
    wei = Account("魏军林",50000,Lock())
    liu = Account("刘畅",10000,Lock())
    
    #转账函数,谁的账户金额摇动,需要先上锁
    #from 要转出的账户,to 转入账户
    import time
    def transfer(from_,to,amount):
        if from_.lock.acquire():    #from账户上锁
            from_.withdraw(amount) #from账户减少
            print(f"{from_._id}转账{amount}元")
            # time.sleep(1)
            if to.lock.acquire():
                to.deposite(amount) #to账户加钱
                to.lock.release() #to账户加钱完毕,解锁
            from_.lock.release() #from账户也转账完毕,解锁
        print(f"{from_._id}成功转了{amount}{to._id}")
    
    # transfer(wei,liu,5000)
    # print(wei.get_balance())
    # print(liu.get_balance())
    #生成两个线程,同时转账
    t1 = Thread(target=transfer,args=(wei,liu,5000)) #魏军林给流畅转5000
    t2 = Thread(target=transfer,args=(liu,wei,500)) #刘畅给魏军林转500
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    #避免死锁
    #尽量避免同一个线程对多个lock进行锁定
    #多个线程对多个lock进行锁定的时候,尽量保证他们以相同的顺序加锁
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    Semaphore :信号量

    最多允许同时N个线程执行内容

    构造方法: Semaphore (N)

    实例方法:

    • acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。
    • release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。
    import threading
    import time
    
    num = 0
    def sum_num(i):
        # lock.acquire()  #获取锁
        with lock:   #类似withopen,自动获取核释放锁
            global num
            print(f"this is thread{i}")
            time.sleep(5)
            num +=i
            print(num)
        # lock.release() #释放锁
    
    lock = threading.BoundedSemaphore(2) #定义了两把钥匙,同时有两个线程去执行
    for i in range(5):
        t = threading.Thread(target = sum_num,args=(i,))
        t.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    Event: 事件锁
    • 事件机制:全局定义了一个“Flag”
    • 如果“Flag”的值为False,那么当程序执行wait方法时就会阻塞
    • 如果“Flag”值为True,那么wait方法时便不再阻塞。
    • 这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时 候,一次性放行所有的排队中的线程。
    • Event是线程间通信最间的机制之一:
    • 一个线程发送一个event信号,其他的线程则等待这个信号。

    用于主线程控制其他线程的执行。

    实例方法:

    • e.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数 timeout)
    • e.set() :将标识位设为Ture
    • e.clear() : 将标识伴设为False
    • e.isSet() :判断标识位是否为Ture
    Condition: 条件锁

    该机制会使得线程等待,只有满足某条件时,才释放n个线程。

    实例方法:

    • wait_for(func): 等待函数返回结果,如果结果为True-放行一个线程
    • wait、lock.notify(N): 一次性放行N个wait
    • acquire、release: 以上的wait和wait_for需要在锁中间使用
    总结
    • 互斥锁 – 允许1个线程执行
    • 信号量 – 允许n个线程执行
    • 事件锁 – 条件变量,满足条件,全部线程都执行
    • 条件锁 – 信号量+事件锁 满足条件,允许N个线程访问执行
    linux通信:互斥锁+信号量+条件变量
    
    • 1

    threading——GIL

    保证同一个进程内同一时刻只有一个线程能执行

    Python GIL与多线程

    • GIL全称Global Interpreter Lock(全局解释器锁)
    • GIL和Python语言没有任何关系,只是因为历史原因导致在官方推荐的解释器Cpython中遗留的问 题(Jpython无此类问题)
    • 每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码

    img

    Python中多线程

    • GIL最基本的行为只有下面两个:
      1. 当前执行的线程持有GIL
      2. 当线程遇到io阻塞时,会释放GIL
    • 由于GIL锁的限制,所以python多线程不适合计算型任务,而更适合IO型任务
      • 计算密集型任务:用CPU、计算 => 多进程
      • IO密集型任务:网络IO(抓取网页数据)、磁盘操作(读写文件)、键盘输入… => 多线程+多进程
  • 相关阅读:
    python判断是否到了文件尾
    PyTorch与向量化计算
    Excel·VBA日期时间转换提取正则表达式函数
    【C语言小游戏】详解三子棋,深刻掌握二维数组
    测试人进阶技能:单元测试报告应用指南
    R语言使用plot函数可视化数据散点图,使用font.axis参数指定坐标轴标签的字体类型为粗体斜体字体(bold italic)
    传输层 拥塞控制-慢开始和拥塞避免 快重传 快恢复
    单片机存储结构简述
    Linux企业运维之k8s(集群部署)
    Android ViewPager2 + Fragment + BottomNavigationView 联动
  • 原文地址:https://blog.csdn.net/a1991376352/article/details/126274662