python-多任务编程

2. 多任务编程

2.1 多任务概述

  • 多任务

    即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq,听音乐,同时上网浏览网页。这是我们看得到的任务,在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统,具备运行多任务的能力。

    在这里插入图片描述

  • 计算机原理

    • CPU:计算机硬件的核心部件,用于对任务进行执行运算。

      在这里插入图片描述

    • 操作系统调用CPU执行任务

      在这里插入图片描述

    • cpu轮询机制 : cpu都在多个任务之间快速的切换执行,切换速度在微秒级别,其实cpu同时只执行一个任务,但是因为切换太快了,从应用层看好像所有任务同时在执行。

    • 多核CPU:现在的计算机一般都是多核CPU,比如四核,八核,我们可以理解为由多个单核CPU的集合。这时候在执行任务时就有了选择,可以将多个任务分配给某一个cpu核心,也可以将多个任务分配给多个cpu核心,操作系统会自动根据任务的复杂程度选择最优的分配方案。

      • 并发 : 多个任务如果被分配给了一个cpu内核,那么这多个任务之间就是并发关系,并发关系的多个任务之间并不是真正的"同时"。
      • 并行 : 多个任务如果被分配给了不同的cpu内核,那么这多个任务之间执行时就是并行关系,并行关系的多个任务时真正的“同时”执行。
  • 什么是多任务编程

    多任务编程即一个程序中编写多个任务,在程序运行时让这多个任务一起运行,而不是一个一个的顺次执行。

    比如微信视频聊天,这时候在微信运行过程中既用到了视频任务也用到了音频任务,甚至同时还能发消息。这就是典型的多任务。而实际的开发过程中这样的情况比比皆是。

    在这里插入图片描述

    • 实现多任务编程的方法 : 多进程编程,多线程编程
  • 多任务意义

    • 提高了任务之间的配合,可以根据运行情况进行任务创建。

      比如: 你也不知道用户在微信使用中是否会进行视频聊天,总不能提前启动起来吧,这是需要根据用户的行为启动新任务。

    • 充分利用计算机资源,提高了任务的执行效率。

      • 在任务中无阻塞时只有并行状态才能提高效率

      在这里插入图片描述

      • 在任务中有阻塞时并行并发都能提高效率

      在这里插入图片描述

2.2 进程(Process)

2.2.1 进程概述
  • 定义: 程序在计算机中的一次执行过程。

    • 程序是一个可执行的文件,是静态的占有磁盘。

    • 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。

      在这里插入图片描述

  • 进程状态

    • 三态
      就绪态 : 进程具备执行条件,等待系统调度分配cpu资源

      	  运行态 : 进程占有cpu正在运行 
         
      	  等待态 : 进程阻塞等待,此时会让出cpu
      

      在这里插入图片描述

    • 五态 (在三态基础上增加新建和终止)

      新建 : 创建一个进程,获取资源的过程

      	  终止 : 进程结束,释放资源的过程
      

      在这里插入图片描述

  • 进程命令

    • 查看进程信息

      ps -aux
      

      在这里插入图片描述

      • USER : 进程的创建者
      • PID : 操作系统分配给进程的编号,大于0的整数,系统中每个进程的PID都不重复。PID也是重要的区分进程的标志。
      • %CPU,%MEM : 占有的CPU和内存
      • STAT : 进程状态信息,S I 表示阻塞状态 ,R 表示就绪状态或者运行状态
      • START : 进程启动时间
      • COMMAND : 通过什么程序启动的进程
    • 进程树形结构

      pstree
      
      • 父子进程:在Linux操作系统中,进程形成树形关系,任务上一级进程是下一级的父进程,下一级进程是上一级的子进程。
2.2.2 多进程编程
  • 使用模块 : multiprocessing

  • 创建流程

    【1】 将需要新进程执行的事件封装为函数

    【2】 通过模块的Process类创建进程对象,关联函数

    【3】 通过进程对象调用start启动进程

  • 主要类和函数使用

    Process()
    功能 : 创建进程对象
    参数 : target 绑定要执行的目标函数 
           args 元组,用于给target函数位置传参
           kwargs 字典,给target函数键值传参
           daemon  bool值,让子进程随父进程退出
    p.start()
    功能 : 启动进程

注意 : 启动进程此时target绑定函数开始执行,该函数作为新进程执行内容,此时进程真正被创建

	p.join([timeout])
	功能:阻塞等待子进程退出
	参数:最长等待时间
进程创建示例:
"""
进程创建示例 01
"""
import multiprocessing as mp
from time import sleep

a = 1   # 全局变量

# 进程目标函数
def fun():
    print("开始运行一个进程")
    sleep(4)  # 模拟事件执行事件
    global a
    print("a =",a)  # Yes
    a = 10000
    print("进程执行结束")


# 实例化进程对象
process = mp.Process(target=fun)

# 启动进程  进程产生 执行fun
process.start()

print("我也做点事情")
sleep(3)
print("我也把事情做完了...")

process.join() # 阻塞等待子进程结束
print("a:",a) # 1  10000
"""
进程创建示例02 : 含有参数的进程函数
"""
from multiprocessing import Process
from time import sleep

# 含有参数的进程函数
def worker(sec,name):
    for i in range(3):
        sleep(sec)
        print("I'm %s"%name)
        print("I'm working....")

# 元组位置传参
# p = Process(target=worker,args=(2,"Tom"))

# 关键字传参
p = Process(target=worker,
            args = (2,),
            kwargs={"name":"Tom"},
            daemon=True) # 子进程伴随父进程结束
p.start()
sleep(3)

  • 进程执行现象理解 (难点)

    • 新的进程是原有进程的子进程,子进程复制父进程全部内存空间代码段,一个进程可以创建多个子进程。
    • 子进程只执行指定的函数,其余内容均是父进程执行内容,但是子进程也拥有其他父进程资源。
    • 各个进程在执行上互不影响,也没有先后顺序关系。
    • 进程创建后,各个进程空间独立,相互没有影响。
    • multiprocessing 创建的子进程中无法使用标准输入(即无法使用input)。
2.2.3 进程处理细节
  • 进程相关函数
    os.getpid()
    功能: 获取一个进程的PID值
    返回值: 返回当前进程的PID 
    os.getppid()
    功能: 获取父进程的PID号
    返回值: 返回父进程PID
    sys.exit(info)
    功能:退出进程
    参数:字符串 表示退出时打印内容
"""
创建多个子进程
"""
from multiprocessing import Process
from time import sleep
import sys, os


def th1():
    sleep(3)
    print("吃饭")
    print(os.getppid(), "--", os.getpid())


def th2():
    # sys.exit("不能睡觉了") # 进程结束
    sleep(1)
    print("睡觉")
    print(os.getppid(), "--", os.getpid())


def th3():
    sleep(2)
    print("打豆豆")
    print(os.getppid(), "--", os.getpid())


# 循环创建子进程
jobs = [] # 存放每个进程对象
for th in [th1, th2, th3]:
    p = Process(target=th)
    jobs.append(p) # 存入jobs
    p.start()

#  确保三件事都结束
for i in jobs:
    i.join()
print("三件事完成")
随堂练习:大文件拆分
有一个大文件,将其拆分成上下两个部分 (按照字节大小),要求两个部分拆分要同步进行
plus : 假设文件很大不要一次read读取全部

提示 : os.path.getsize() 获取文件大小
       创建两个子进程分别拆上下两个部分

import os
from multiprocessing import Process

filename = "./dict.txt"
size = os.path.getsize(filename)

# 如果父进程打开子进程直接用则会公用一个文件偏移量
# fr = open(filename,'rb')

def top():
    fr = open(filename,'rb')
    fw = open('top.txt','wb')
    n = size // 2
    while n >= 1024:
        fw.write(fr.read(1024))
        n -= 1024
    else:
        fw.write(fr.read(n))
    fr.close()
    fw.close()

def bot():
    fr = open(filename, 'rb')
    fw = open('bot.txt', 'wb')
    fr.seek(size//2,0)  # 文件偏移量到中间
    while True:
        data = fr.read(1024)
        if not data:
            break
        fw.write(data)
    fr.close()
    fw.close()

jobs=[]
for item in [top,bot]:
    p = Process(target=item)
    jobs.append(p)
    p.start()

[i.join() for i in jobs]
print("拆分完成")


  • 孤儿进程和僵尸进程

    • 孤儿进程: 父进程先于子进程退出时,子进程会成为孤儿进程,孤儿进程会被系统自动收养,成为孤儿进程新的父进程,并在孤儿进程退出时释放其资源。

    • 僵尸进程: 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会成为僵尸进程。

      特点: 僵尸进程虽然结束,但是会存留部分进程资源在内存中,大量的僵尸进程会浪费系统资源。Python模块当中自动建立了僵尸处理机制,每次创建新进程都进行检查,将之前产生的僵尸处理掉,而且父进程退出前,僵尸也会被自动处理。

2.2.4 创建进程类

进程的基本创建方法将子进程执行的内容封装为函数。如果我们更热衷于面向对象的编程思想,也可以使用类来封装进程内容。

  • 创建步骤

    【1】 继承Process类

    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性

    【3】 重写run()方法

  • 使用方法

    【1】 实例化对象

    【2】 调用start自动执行run方法

    """
    自定义进程类
    """
    from multiprocessing import Process
    from time import sleep
    
    
    class MyProcess(Process):
        def __init__(self, value):
            self.value = value
            super().__init__()  # 调用父类的init
    
        # 重写run 作为进程的执行内容
        def run(self):
            for i in range(self.value):
                sleep(2)
                print("自定义进程类。。。。")
    
    p = MyProcess(3)
    p.start() # 将 run方法作为进程执行
    
    随堂练习:
    1.100000以内质数之和,并且计算这个求和过程的时间
    
    2.100000分成4份,创建4个进程,每个进程求其中一份的
    质数之和,统计4个进程执行完的时间
    
    提示: 
    质数: 只能被1和其本身整除的整数 >1
    import time  time.time()
    
    import time
    from multiprocessing import Process
    
    # 求begin -- end 之间的质数之和
    class Prime(Process):
        @staticmethod
        def is_prime(n):
            if n <= 1:
                return False
            for i in range(2, n // 2 + 1):
                if n % i == 0:
                    return False
            return True
    
        def __init__(self,begin,end):
            self.begin = begin # 起始数字
            self.end = end # 结尾数字
            super().__init__()
    
        def run(self):
            prime = []  # 存放所有质数
            for i in range(self.begin,self.end):
                if Prime.is_prime(i):
                    prime.append(i)  # 存入列表
            print(sum(prime))
    
    if __name__ == '__main__':
        # 4进程 用时: 9.434056282043457
        # 10进程 用时: 8.70681643486023
        jobs = []
        b = time.time()
        for i in range(1,100001,10000):
            p = Prime(i,i + 10000)
            jobs.append(p)
            p.start()
        [i.join() for i in jobs]
        print("用时:",time.time()-b)
    
    
    
    # def is_prime(n):
    #     if n <= 1:
    #         return False
    #     for i in range(2,n // 2 + 1):
    #         if n % i == 0:
    #             return False
    #     return True
    
    # def prime_sum():
    #     prime = [] # 存放所有质数
    #     for i in range(100001):
    #         if is_prime(i):
    #             prime.append(i)  # 存入列表
    #     print(sum(prime))
    
    # 用时: 16.357502222061157
    # begin = time.time()
    # prime_sum()
    # print("用时:",time.time()-begin)
    
2.2.5 进程间通信
  • 必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。

  • 常用进程间通信方法:消息队列,套接字等。

  • 消息队列使用

    • 通信原理: 在内存中开辟空间,建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
    • 实现方法
    from multiprocessing import Queue
    
    q = Queue(maxsize=0)
    功能: 创建队列对象
    参数:最多存放消息个数
    返回值:队列对象
    
    q.put(data)
    功能:向队列存入消息
    参数:data  要存入的内容
    
    q.get()
    功能:从队列取出消息
    返回值: 返回获取到的内容
    
    q.full()   判断队列是否为满
    q.empty()  判断队列是否为空
    q.qsize()  获取队列中消息个数
    q.close()  关闭队列
    
进程间通信示例:
from multiprocessing import Process,Queue

# 创建消息队列
q = Queue(5)

# 子进程函数
def handle():
    while True:
        cmd = q.get() # 取出指令
        if cmd == "1":
            print("\n完成指令1")
        elif cmd == "2":
            print("\n完成指令2")

# 创建进程
p = Process(target=handle,daemon=True)
p.start()

while  True:
    cmd = input("指令:")
    if not cmd:
        break
    q.put(cmd) # 通过队列给子进程

随堂练习:
有一个目录中有若干普通文件,将该目录复制一份到当前程序所在位置
要求: 目标文件夹中每个文件复制都采用一个独立的进程完成
      当所有文件复制完成之后,按复制完成顺序打印所有文件名
提示:
创建文件夹 : os.mkdir(dir)  os.listdir()

from multiprocessing import Process, Queue
import os

# 消息队列
q = Queue()

# 进程函数  复制文件
def copy(old,new,file):
    fr = open(old+'/'+file,'rb')
    fw = open(new+'/'+file,'wb')
    while True:
        data = fr.read()
        if not data:
            break
        fw.write(data)
    fr.close()
    fw.close()
    q.put(file) #  存入队列

# 入口函数
def main(old):
    new = old.split('/')[-1]
    os.mkdir(new)  #  创建新文件夹
    jobs = []  # 存放每个进程对象
    # 循环创建进程 file --> 文件名称
    for file in os.listdir(old):
        p = Process(target=copy,args=(old,new,file))
        jobs.append(p)
        p.start()
    [i.join() for i in jobs] # 判断所有进程结束
    print("拷贝了如下文件:")
    while q.qsize():
        print(q.get())

if __name__ == '__main__':
    main("/home/tarena/FTP")

**群聊聊天室 **

功能 : 类似qq群功能

【1】 有人进入聊天室需要输入姓名,姓名不能重复

【2】 有人进入聊天室时,其他人会收到通知:Lucy 进入了聊天室

【3】 一个人发消息,其他人会收到: Lucy : 一起出去玩啊。

【4】 有人退出聊天室,则其他人也会收到通知 : Lucy 退出了聊天室

【5】 扩展功能:服务器可以向所有用户发送公告: 管理员消息: 大家好,欢迎进入聊天室。

################ 服务端参考代码 ###################
from socket import *
from multiprocessing import Process

# 服务器地址
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)

# 存储用户信息 {name:address}
user = {}


# 处理进入聊天室
def login(sock, name, address):
    if name in user or "管理" in name:
        sock.sendto(b"FAIL", address)
    else:
        sock.sendto(b"OK", address)
        # 告知其他人
        msg = "欢迎 %s 进入聊天室" % name
        for key, value in user.items():
            sock.sendto(msg.encode(), value)
        user[name] = address  # 存储用户
        # print(user)  # 测试


# 处理聊天
def chat(sock, name, content):
    msg = "%s : %s" % (name, content)
    for key, value in user.items():
        # 不是本人就发送
        if key != name:
            sock.sendto(msg.encode(), value)


# 处理退出
def exit(sock, name):
    if name in user:
        del user[name]  # 删除该用户
    # 通知其他用户
    msg = "%s 退出聊天室" % name
    for key, value in user.items():
        sock.sendto(msg.encode(), value)


def handle(sock):
    # 不断接收请求,分情况讨论
    while True:
        request, addr = sock.recvfrom(1024)
        tmp = request.decode().split(" ", 2)
        # 分情况讨论
        if tmp[0] == "LOGIN":
            # tmp ->[LOGIN,name]
            login(sock, tmp[1], addr)
        elif tmp[0] == "CHAT":
            # tmp ->[CHAT,name,content]
            chat(sock, tmp[1], tmp[2])
        elif tmp[0] == "EXIT":
            # tmp ->[EXIT,name]
            exit(sock, tmp[1])


# 程序入口函数
def main():
    # 创建udp
    sock = socket(AF_INET, SOCK_DGRAM)
    sock.bind(ADDR)

    # 接收请求,分类处理
    p = Process(target=handle, args=(sock,), daemon=True)
    p.start()

    while True:
        content = input("管理员消息:")
        if not content:
            break
        msg = "CHAT 管理员消息 " + content
        # 从父进程发送到子进程
        sock.sendto(msg.encode(), ADDR)


if __name__ == '__main__':
    main()

    
################## 客户端参考代码 ##################
from socket import *
from multiprocessing import Process
import sys

# 服务器地址
SERVER_ADDR = ("XX.XX.XXX.XX", 8888)


def login(sock):
    while True:
        name = input("请输入昵称:")
        # 组织请求
        msg = "LOGIN " + name
        sock.sendto(msg.encode(), SERVER_ADDR)
        result, addr = sock.recvfrom(1024)
        if result == b"OK":
            print("进入聊天室")
            return name
        else:
            print("该昵称已存在")


# 子进程接收函数
def recv_msg(sock):
    while True:
        data, addr = sock.recvfrom(1024 * 10)
        # 格式处理
        content = "\n" + data.decode() + "\n发言:"
        print(content, end="")


#  父进程发送函数
def send_msg(sock, name):
    while True:
        try:
            content = input("发言:")
        except KeyboardInterrupt:
            content = "exit"
        # 表示退出
        if content == 'exit':
            msg = "EXIT " + name
            sock.sendto(msg.encode(), SERVER_ADDR)
            sys.exit("您已退出聊天室")
        msg = "CHAT %s %s" % (name, content)
        sock.sendto(msg.encode(), SERVER_ADDR)


def main():
    sock = socket(AF_INET, SOCK_DGRAM)
    sock.bind(("0.0.0.0",55224)) # 端口不要变
    name = login(sock)  # 请求进入聊天室
    # 子进程负责接收
    p = Process(target=recv_msg, args=(sock,), daemon=True)
    p.start()
    send_msg(sock, name)  # 发送消息


if __name__ == '__main__':
    main()

2.3 线程 (Thread)

2.3.1 线程概述
  • 什么是线程

    【1】 线程被称为轻量级的进程,也是多任务编程方式

    【2】 也可以利用计算机的多cpu资源

    【3】 线程可以理解为进程中再开辟的分支任务

  • 线程特征

    【1】 一个进程中可以包含多个线程

    【2】 线程也是一个运行行为,消耗计算机资源

    【3】 一个进程中的所有线程共享这个进程的资源

    【4】 多个线程之间的运行同样互不影响各自运行

    【5】 线程的创建和销毁消耗资源远小于进程

    在这里插入图片描述

2.3.2 多线程编程
  • 线程模块: threading

在这里插入图片描述

  • 创建方法

    【1】 创建线程对象

from threading import Thread 

t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
     args   元组 给线程函数位置传参
     kwargs 字典 给线程函数键值传参
     daemon bool值,主线程推出时该分支线程也推出
【2】 启动线程
 t.start()
【3】等待分支线程结束
t.join([timeout])
功能:阻塞等待分支线程退出
参数:最长等待时间
线程示例01import threading
from time import sleep
import os

a = 1

#  线程函数
def music():
    global a
    print("a =",a)
    a = 10000
    for i in range(3):
        sleep(2)
        print(os.getpid(),"播放:黄河大合唱")

# 实例化线程对象
thread = threading.Thread(target=music)
# 启动线程 线程存在
thread.start()

for i in range(4):
    sleep(1)
    print(os.getpid(),"播放:葫芦娃")

# 阻塞等待分支线程结束
thread.join()
print("a:",a)
线程示例02from threading import Thread
from time import sleep

# 带有参数的线程函数
def func(sec,name):
    print("含有参数的线程来喽")
    sleep(sec)
    print("%s 线程执行完毕"%name)

# 循环创建线程
for i in range(5):
    t = Thread(target=func,
               args=(2,),
               kwargs={"name":"T-%d"%i},
               daemon=True)
    t.start()
2.3.3 创建线程类
  1. 创建步骤

    【1】 继承Thread类

    【2】 重写__init__方法添加自己的属性,使用super()加载父类属性

    【3】 重写run()方法

  2. 使用方法

    【1】 实例化对象

    【2】 调用start自动执行run方法

    from threading import Thread
    from time import sleep
    
    class MyThread(Thread):
        def __init__(self,song):
            self.song = song
            super().__init__() # 得到父类内容
    
        # 线程要做的事情
        def run(self):
            for i in range(3):
                sleep(2)
                print("播放:",self.song)
    
    t = MyThread("凉凉")
    t.start() # 运行run
    
    随堂练习:
    现在有500张票,存在一个列表中 ["T1",...."T500"]10个窗口同时卖这500张票 W1-W10
    
    使用10个线程模拟这10个窗口,同时卖票,直到所有的票都卖出为止,每出一张票 需要0.1秒,打印表示即可print("W1----T250")
    
    from threading import Thread,Lock
    from time import sleep
    
    lock = Lock() # 创建锁
    
    # 将票准备好
    ticket = ["T%d" % x for x in range(1, 501)]
    
    # 线程函数 w:表示窗口
    def sell(w):
        while ticket:
            print("%s --- %s"%(w,ticket.pop(0)))
            sleep(0.1)
    
    # 10个线程
    for i in range(1,11):
        t = Thread(target=sell,args=("W%d"%i,))
        t.start()
    
2.3.4 线程同步互斥
  • 线程通信方法: 线程间使用全局变量进行通信

  • 共享资源争夺

    • 共享资源:多线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
    • 影响 : 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。
  • 同步互斥机制

    • 同步 : 同步是一种协作关系,为完成操作,线程间形成一种协调,按照必要的步骤有序执行操作。

      在这里插入图片描述

    • 互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。

在这里插入图片描述

  • 线程Event
from threading import Event

e = Event()  创建线程event对象

e.wait([timeout])  阻塞等待e被set

e.set()  设置e,使wait结束阻塞

e.clear() 使e回到未被设置状态

e.is_set()  查看当前e是否被设置
Event使用示例:

from threading import Thread, Event

msg = None  # 通信变量
e = Event()  # 事件对象


def 杨子荣():
    print("杨子荣前来拜山头")
    global msg
    msg = "天王盖地虎"
    e.set()  # 通知主线程可以判断


t = Thread(target=杨子荣)
t.start()

print("说对口令才是自己人")
e.wait()  # 阻塞等待通知
if msg == "天王盖地虎":
    print("宝塔镇河妖")
    print("确认过眼神你是对的人")
else:
    print("打死他.... 无情啊 哥哥....")

  • 线程锁 Lock
from  threading import Lock

lock = Lock()  创建锁对象
lock.acquire() 上锁  如果lock已经上锁再调用会阻塞
lock.release() 解锁
Lock使用示例:

from threading import Thread, Lock

lock = Lock() # 创建锁
a = b = 0

def value():
    while True:
        lock.acquire() # 上锁
        if a != b:
            print("a = %d,b = %d" % (a, b))
        lock.release() # 解锁

t = Thread(target=value)
t.start()

while True:
    lock.acquire()
    a += 1
    b += 1
    lock.release()

随堂练习:
使用两个分支线程,一个线程打印1-5252个数字,另一个线程打印A-Z 这26个字母。要求同时执行两个线程,打印顺序为: 12A34B....5152Z

from threading import Thread,Lock

lock1 = Lock()
lock2 = Lock()

def print_num():
    for i in range(1,53,2):
        lock1.acquire()
        print(i)
        print(i + 1)
        lock2.release()

def print_chr():
    for i in range(65,91):
        lock2.acquire()
        print(chr(i))
        lock1.release()


t1 = Thread(target=print_num)
t2 = Thread(target=print_chr)

lock2.acquire() # 先把打印字母的部分锁住

t1.start()
t2.start()
2.3.5 死锁
  • 什么是死锁

    死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

在这里插入图片描述

  • 死锁产生条件

    • 互斥条件:指线程使用了互斥方法,使用一个资源时其他线程无法使用。

    • 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,在获取到新的资源前不会释放自己保持的资源。

    • 不剥夺条件:不会受到线程外部的干扰,如系统强制终止线程等。

    • 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如 T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。

  • 如何避免死锁

    • 逻辑清晰,不要同时出现上述死锁产生的四个条件
    • 通过测试工程师进行死锁检测
死锁现象演示:

"""
死锁现象演示
"""
from time import sleep
from threading import Thread,Lock

# 账户类
class Account:
    def __init__(self,id,balance,lock):
        self._id = id
        self._balance = balance
        self.lock = lock

    # 取钱
    def withdraw(self,amount):
        self._balance -= amount

    # 存钱
    def deposit(self,amount):
        self._balance += amount

    # 查看余额
    def getBalance(self):
        return self._balance


# 转账函数
def transfer(from_,to,amount):
    from_.lock.acquire()
    from_.withdraw(amount) # from_钱减少
    from_.lock.release() # 不会产生死锁
    sleep(0.1) # 网络延迟

    to.lock.acquire()
    to.deposit(amount) # to钱增加

    # from_.lock.release() # 产生死锁
    to.lock.release()

if __name__ == '__main__':
    tom = Account("Tom",5000,Lock())
    abby = Account("abby",8000,Lock())

    t1 = Thread(target=transfer,args=(tom,abby,2000))
    t2 = Thread(target=transfer,args=(abby,tom,3000))
    t1.start()
    t2.start()
    t1.join()
    t2.join()


    print("Tom:",tom.getBalance())
    print("Abby:",abby.getBalance())


2.3.6 GIL问题
  • 什么是GIL问题 (全局解释器锁)

    由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。

  • 导致后果
    因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升。

  • 关于GIL问题的处理

    • 尽量使用进程完成无阻塞的并发行为

    • 不使用c作为解释器 (可以用Java C#)

    Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235

  • 结论

    • GIL问题与Python语言本身并没什么关系,属于解释器设计的历史问题。
    • 在无阻塞状态下,多线程程序程序执行效率并不高,甚至还不如单线程效率。
    • Python多线程只适用于执行有阻塞延迟的任务情形。
线程效率对比进程实验:

class Prime(Thread):
    # 判断一个数是否为质数
    @staticmethod
    def is_prime(n):
        if n <= 1:
            return False
        for i in range(2,n // 2 + 1):
            if n % i == 0:
                return False
        return True

    def __init__(self,begin,end):
        self.__begin = begin
        self.__end = end
        super().__init__()

    def run(self):
        prime = [] # 存放所有质数
        for i in range(self.__begin,self.__end):
            if Prime.is_prime(i):
                prime.append(i)
        print(sum(prime))

@timeis
def process_10():
    jobs = []
    for i in range(1,100001,10000):
        t = Prime(i,i + 10000)
        jobs.append(t)
        t.start()
    for i in jobs:
        i.join()


if __name__ == '__main__':
    process_10()
2.3.7 进程线程的区别联系
  • 区别联系
  1. 两者都是多任务编程方式,都能使用计算机多核资源
  2. 进程的创建删除消耗的计算机资源比线程多
  3. 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
  4. 一个进程可以有多个分支线程,两者有包含关系
  5. 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
  6. Python线程存在GIL问题,但是进程没有。
  • 使用场景

    在这里插入图片描述

  1. 任务场景:一个大型服务,往往包含多个独立的任务模块,每个任务模块又有多个小独立任务构成,此时整个项目可能有多个进程,每个进程又有多个线程。
  2. 编程语言:Java,C#之类的编程语言在执行多任务时一般都是用线程完成,因为线程资源消耗少;而Python由于GIL问题往往使用多进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/842805.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国产麒麟、UOS在线打开pdf加盖印章

PageOffice支持两种电子印章方案&#xff0c;可实现对Word、Excel、PDF文档加盖PageOffice自带印章或ZoomSeal电子印章&#xff08;全方位保护、防篡改、防伪造&#xff09;。Word和Excel的盖章功能请参考&#xff1a;Word和Excel加盖印章和签字功能 &#xff08;目前只支持win…

【Django+Vue3 线上教育平台项目实战】Celery赋能:优化订单超时处理与自动化定时任务调度

文章目录 前言⭐✨&#x1f4ab;&#x1f525;&#x1f4d6;一、Celery⭐1.基本概念及介绍:✨2.使用步骤&#x1f4ab; 二、订单超时 取消订单&#xff08;Celery&#xff09;&#x1f525;具体实现流程&#x1f4d6; 前言⭐✨&#x1f4ab;&#x1f525;&#x1f4d6; 在构建复…

Mac Electron 应用如何进行签名(signature)和公证(notarization)?

最近很多客户反映&#xff0c;从官网下载的Mac Electron应用打不开&#xff0c;直接报病毒&#xff0c;类似于这种&#xff1a; 这是因为在MacOS 10.14.5之后&#xff0c;如果应用没有在苹果官方平台进行公证notarization(我们可以理解为安装包需要审核&#xff0c;来判断是否存…

Typora 1.5.8 版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora是一款基于Markdown语法的轻量级文本编辑器&#xff0c;它的主要目标是为用户提供一个简洁、高效的写作环境。以下是Typora的一些主要特点和功能&#xff1a; 实时预览&#xff1a;Typora支持实时预览功能&#xff0…

【每日一练】python编写一个简易计算器

程序代码: #循环语句&#xff0c;条件为真所以循环执行 while True: #定义两个数的变量和运算符号 num1 float(input("第一个数:")) num2 float(input("第一个数:")) syminput("选择运算符 - * /&#xff1a;") #判断运算符号 …

知名在线市场 Etsy 允许在其平台上销售 AI 艺术品,但有条件限制|TodayAI

近日&#xff0c;以手工和复古商品著称的在线市场 Etsy 宣布&#xff0c;将允许在其平台上销售 AI 生成的艺术品。这一举措引发了广泛关注和争议。尽管 Etsy 正在接受 AI 艺术的潮流&#xff0c;但平台对这一类商品的销售设置了一些限制。 根据 Etsy 新发布的政策&#xff0c;…

小程序图片下载保存方法,图片源文件保存!

引言 现在很多时候我们在观看到小程序中的图片的时候&#xff0c;想保存图片的原文件格式的话&#xff0c;很多小程序是禁止保存的&#xff0c;即使是让保存的话&#xff0c;很多小程序也会限制不让保存原文件&#xff0c;只让保存一些分辨率很低的&#xff0c;非常模糊的图片…

【iOS】类对象的结构分析

目录 对象的分类object_getClass和class方法isa流程和继承链分析isa流程实例验证类的继承链实例验证 类的结构cache_t结构bits分析实例验证属性properties方法methods协议protocolsro类方法 类结构流程图解 对象的分类 OC中的对象主要可以分为3种&#xff1a;实例对象&#xf…

tinymce富文本支持word内容同时粘贴文字图片上传 vue2

效果图 先放文件 文件自取tinymce: tinymce富文本简单配置及word内容粘贴图片上传 封装tinymce 文件自取&#xff1a;tinymce: tinymce富文本简单配置及word内容粘贴图片上传 页面引用组件 <TinymceSimplify refTinymceSimplify v-model"knowledgeBlockItem.content…

exo 大模型算力共享;Llama3-70B是什么

目录 exo 大模型算力共享 exo框架的特点 如何使用exo框架 注意事项 结论 Llama3-70B是什么 一、基本信息 二、技术特点 三、性能与应用 四、未来发展 exo 大模型算力共享 exo框架的特点 异构支持:支持多种不同类型的设备,包括智能手机、平板电脑、笔记本电脑以及高…

仅两家!云原生向量数据库 PieCloudVector 全项通过信通院「可信数据库」评测

7月16日&#xff0c;2024 可信数据库发展大会在北京隆重举行。大会以“自主、创新、引领”为主题&#xff0c;近百位数据库领域的专家、学者齐聚一堂&#xff0c;带来高质量的数据库技术洞察与实战经验。 本次可信数据库发展大会中&#xff0c;中国信通院正式公布 2024 年上半年…

SpringMVC源码深度解析(中)

接上一遍博客《SpringMVC源码深度解析(上)》继续聊。最后聊到了SpringMVC的九大组建的初始化&#xff0c;以 HandlerMapping为例&#xff0c;SpringMVC提供了三个实现了&#xff0c;分别是&#xff1a;BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctio…

公司技术栈用到了RocketMQ,我对此块知识进行了回顾(初始RocketMQ)

前言 作为24届的校招生&#xff0c;不知道大伙儿们是否都已经到了工作岗位上。为了以后更方便的接触到公司的业务&#xff0c;我司为我们安排了将近一个月的实操。虽然不用敲代码&#xff0c;但是… 了解到我司使用到的技术栈&#xff0c;在空闲时间正好对RocketMQ这块技术做个…

[PM]产品运营

生命周期 运营阶段 主要工作 拉新 新用户的定义 冷启动 拉新方式 促活 用户活跃的原因 量化活跃度 运营社区化/内容化 留存 用户流失 培养用户习惯 用户挽回 变现 变现方式 付费模式 广告模式 数据变现 变现指标 传播 营销 认识营销 电商营销中心 拼团活动 1.需求整理 2.…

使用LVS+NGinx+Netty实现数据接入

数据接入 链接参考文档 LVSKeepalived项目 车辆数据上收&#xff0c;TBox通过TCP协议连接到TSP平台 建立连接后进行数据上传。也可借由该连接实现远程控制等操作。 通过搭建 LV—NGinx—Netty实现高并发数据接入 LVS&#xff1a;四层负载均衡&#xff08;位于内核层&#x…

【数据结构进阶】二叉搜索树

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; C || 数据结构 目录 &#x1f308;前言&#x1f525;二叉搜索树&#x1f525; 二叉搜索树的实现Insert&#xff08;插入&#xff09;find&#xff08;查找&#xff09;erase(删除)destro…

【中项】系统集成项目管理工程师-第2章 信息技术发展-2.1信息技术及其发展-2.1.1计算机软硬件与2.1.2计算机网络

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

车载音视频App框架设计

简介 统一播放器提供媒体播放一致性的交互和视觉体验&#xff0c;减少各个媒体应用和场景独自开发的重复工作量&#xff0c;实现媒体播放链路的一致性&#xff0c;减少碎片化的Bug。本文面向应用开发者介绍如何快速接入媒体播放器。 主要功能&#xff1a; 新设计的统一播放U…

Windows FFmpeg 开发环境搭建

FFmpeg 开发环境搭建 FFmpeg命令行环境搭建使用FFmpeg官方编译的库Windows编译FFmpeg1. 下载[msys2](https://www.msys2.org/#installation)2. 安装完成之后,将安装⽬录下的msys2_shell.cmd中注释掉的 rem set3. 修改pacman 镜像源并安装依赖4. 下载并编译源码 FFmpeg命令行环境…

【C++】 string类的模拟实现

目录 一、我们先创建三个文件分别为 String.h&#xff08;声明&#xff09;、String.cpp&#xff08;定义&#xff09;、teat.cpp&#xff08;测试&#xff09; 二、成员函数 构造函数与析构函数 &#x1f31f;string() &#x1f31f;string(const char* str) &#x1f…