IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> Python高级软件技术(一) -> 正文阅读

[数据结构与算法]Python高级软件技术(一)

P1 排序算法

1. 选择排序

1.1 算法步骤

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  3. 重复第二步,直到所有元素均排序完成

1.2 代码实现

def selectionSort(arr):
    for i in range(len(arr) - 1):
        # 记录最小数的索引
        minIndex = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[minIndex]:
                minIndex = j
        # i 不是最小数时,将 i 和最小数进行交换
        if i != minIndex:
            arr[i], arr[minIndex] = arr[minIndex], arr[i]
    return arr

2. 快速排序

2.1 算法步骤

  1. 从数列中挑出一个元素,称为基准元素(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序

2.2 代码实现

def quickSort(arr, left=None, right=None):
    left = 0 if not isinstance(left,(int, float)) else left
    right = len(arr)-1 if not isinstance(right,(int, float)) else right
    if left < right:
        partitionIndex = partition(arr, left, right)
        quickSort(arr, left, partitionIndex-1)
        quickSort(arr, partitionIndex+1, right)
    return arr

def partition(arr, left, right):
    pivot = left
    index = pivot+1
    i = index
    while  i <= right:
        if arr[i] < arr[pivot]:
            swap(arr, i, index)
            index+=1
        i+=1
    swap(arr,pivot,index-1)
    return index-1

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]
# 完成一轮交换
def sub_sort(list_, low, high):
    temp = list_[low]  # 选定基准
    while low < high:
        while list_[high] >= temp and high > low:
            high -= 1
        list_[low] = list_[high]
        while list_[low] < temp and low < high:
            low += 1
        list_[high] = list_[low]
    list_[low] = temp
    return low


def quicksort(list_, low, high):
    """
    快速排序
    :param list_: 待排序的列表
    :param low: 列表第一个元素索引
    :param high:最后一个元素索引
    :return:排完序的列表
    """
    if low < high:
        key = sub_sort(list_, low, high)
        quicksort(list_, low, key - 1)
        quicksort(list_, key + 1, high)
    return list_

3. 插入排序

3.1 算法步骤

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

3.2 代码实现

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

P2 IO网络编程

1. 字节串与字符串

  1. 普通的ASCII编码字符串可以在前面加b转换为字节串,例如:b’hello’
  2. 字符串转换为字节串方法:str.encode()
  3. 字节串转换为字符串方法:bytes.decode()

2. 文件操作

获取文件对象后可以通过文件对象对文件进行各种操作,文件对象可以调用下列函数:

2.1 读取文件

  • read([size])
  • readline([size])
  • readlines([sizeint])

如果文件对象是以读的方式获取的,该文件对象是一个可迭代对象,在for循环中可以迭代文件的每一行

f = open('test', 'r')

while True:
    # 读到文件结尾返回空字符串
    data = f.read(100)  # 每次最多读100字符
    if not data:
        break
    print(data)

2.2 with操作

??python中的with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会执行规定的“清理”操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁的自动获取和释放等。

with语句的语法格式如下

with context_expression [as target(s)]:
		with-body

??通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用

with open('file', 'r+') as f:
	r.read()

3. 文件偏移量

3.1 定义

??打开一个文件进行操作时系统会自动生成一个记录,记录中描述了我们对文件的一系列操作。其中包括每次操作到的文件位置。文件的读写操作都是从这个位置开始进行的。

3.2 基本操作

3.2.1 tell()

??功能: 获取文件偏移量大小

3.2.2 seek(offset[,whence])

??功能:移动文件偏移量位置
??参数:offset代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。whence是基准位置,默认值为0,代表从文件开头算起,1代表从当前位置算起,2表示从文件末尾算起。
??注意:必须以二进制方式打开文件时基准位置才能是1或者2

4. 文件描述符

4.1 定义

系统中每一个IO操作都会分配一个整数作为编号,该整数即这个IO操作的文件描述符

4.2 获取文件描述符

fileno()
??通过IO对象获取对应的文件描述符

4.3 文件管理函数

  1. 获取文件大小
    os.path.getsize(file)
  2. 查看文件列表
    os.listdir(dir)
  3. 查看文件是否存在
    os.path.exists(file)
  4. 判断文件类型
    os.path.isfile(file)
  5. 删除文件
    os.remove(file)

5. 网络模型

5.1 OSI七层模型

制定组织
??ISO(国际标准化组织)
作用
??使网络通信工作流程标准化

应用层: 提供用户服务,具体功能由应用程序实现
表示层: 数据的压缩优化加密
会话层: 建立用户级的连接,选择适当的传输服务
传输层: 提供传输服务
网络层: 路由选择,网络互联
链路层: 进行数据交换,控制具体数据的发送
物理层: 提供数据传输的硬件保证,网卡接口,传输介质

优点:

  1. 建立了统一的工作流程
  2. 分部清晰,各司其职,每个步骤分工明确
  3. 降低了各个模块之间的耦合度,便于开发

5.2 TCP/IP四层模型

  • 应用层
  • 传输层
  • 网际层
  • 网络接口

网络协议

在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等

5.3 网络基础概念

IP地址

  • 功能:确定一台主机的网络路由位置
  • 查看本机网络地址命令:ifconfig

域名

  • 定义:给网络服务器地址起的名字
  • 作用:方便记忆,表达一定的含义
  • ping[ip]:测试和某个主机是否联通

端口号(port)

  • 作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序
  • 特点:一个系统中的应用监听端口不能重复
  • 取值范围:1–65535
  • 1–1023系统应用或者大众程序监听端口
  • 1024–65535自用端口

5.4 传输层服务

5.4.1 面向连接的传输服务(基于TCP协议的数据传输)

传输特征:

  • 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复

实现手段:

  • 在通信前需要建立数据连接,通信结束要正常断开连接

三次握手(建立连接)

  1. 客户端向服务器发送消息报文请求连接
  2. 服务器收到请求后,回复报文确定可以连接
  3. 客户端收到回复,发送最终报文连接建立

四次挥手(断开连接)

  1. 主动方发送报文请求断开连接
  2. 被动方收到消息后,立即回复,表示准备断开
  3. 被动方准备就绪,再次发送报文表示可以断开
  4. 主动方收到确定,发送最终报文完成断开

5.4.2 面向无连接的传输服务(基于UDP协议的数据传输)

传输特点:

  • 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。

适用情况:

  • 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播

5.5 socket套接字编程

5.5.1 套接字介绍

套接字:

  • 实现网络编程进行数据传输的一种技术手段

Python实现套接字编程:

  • import socket
  • from socket import socket

套接字分类

  1. 流式套接字(SOCK_STREAM):以字节流方式传输数据,实现tcp网络传输方案
  2. 数据报套接字:以数据报形式传输数据,实现udp网络传输方案。

5.5.2 tcp套接字

服务端流程:
??socket–>bind–>listen–>accept–>send/recv–>close
1. 创建套接字

sockfd = socket.socket(socket_family = AF_INET, socket_type = SOCK_STREAM, proto=0)
# 功能:创建套接字
# 参数socket_family	网络地址类型	AF_INET表示ipv4
# 参数socket_type	套接字类型	SOCK_STREAM(流式)
# proto	通常为0	选择子协议

2. 绑定地址

  • 本地地址:localhost 127.0.0.1
  • 网络地址:172.40.91.185
  • 自动获取地址:0.0.0.0
sockfd.bind(addr)
# 功能:绑定本机网络地址
# 参数:二元元组(ip, port)	('0.0.0.0',8888)

在这里插入图片描述
3. 设置监听

sockfd.listen(n)
# 功能:将套接字设置为监听套接字,确定监听队列大小
# 参数:监听队列大小

4.等待处理客户端连接请求

connfd, addr = sockfd.accept()
# 功能:阻塞等待处理客户端请求
# 返回值:connfd	客户端连接套接字
# addr	连接的客户端地址

5.消息收发

data = connfd.recv(buffersize)
#功能:接收客户端消息
#参数:每次最多接收消息的大小
#返回值:接收到的内容

n = connfd.send(data)
#功能:发送消息
#参数:要发送的内容	bytes格式
#返回值:发送的字节数

6.关闭套接字

sockfd.close()
# 功能:关闭套接字

代码示例

import socket

# 创建套接字对象
import unicodedata

sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定地址
sockfd.bind(('127.0.0.1', 8888))

# 设置监听套接字
sockfd.listen(5)

while True:

    # 阻塞等待客户端连接
    print("Waiting for connect")
    try:
        connfd, addr = sockfd.accept()
        print("Connect from", addr)
    except KeyboardInterrupt:
        print("Server Exit")
        break
    except Exception as e:
        print(e)
        continue

    # 收发消息
    while True:
        data = connfd.recv(1024)
        if not data:  # data为空说明客户端退出
            break
        print("收到:", data.decode())
        n = connfd.send(b'Thanks')
        print("发送%d字节" % n)

    # 关闭套接字
    connfd.close()
sockfd.close()

客户端流程
??socket–>bind<可选>(一般不写)–>connect–>send/recv–>close

1.创建套接字

  • 注意:只有相同类型的套接字才能进行通信

2.连接服务器

sockfd.connect((server_addr,port))
# 功能:连接服务器
# 参数:元组	服务器地址

3.收发消息

  • 注意:防止两端都阻塞,recv send要配合

4.关闭套接字

代码示例

"""
tcp_client.py   tcp客户端流程
"""

from socket import *

# 创建tcp套接字
sockfd = socket()  # 使用默认参数-->tcp套接字

# 连接服务器
server_addr = ('127.0.0.1', 8888)

sockfd.connect(server_addr)

# 收发消息
while True:
    data = input("Msg>>")
    if not data:
        break
    sockfd.send(data.encode())
    data = sockfd.recv(1024)
    print("Server:", data.decode())  # 打印接收内容

# 关闭套接字
sockfd.close()

5.5.3 tcp套接字数据传输特点

??tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串
tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
一个监听套接字可以同时连接多个客户端,也能够重复被连接

5.5.4 网络收发缓冲区

  1. 网络缓冲区有效的协调了消息的收发速度
  2. send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞

5.5.5 tcp粘包

原因:

  • tcp以字节流方式传输,无消息边界。多次发送的消息被一次接收,此时就会形成粘包

影响:

  • 如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响

处理方法:

  1. 人为的添加消息边界
  2. 控制发送速度

5.5.6 UDP套接字

服务端流程
??socket–>bind–>recvfrom–>sendto–>close

1.创建数据报套接字

sockfd = sock(AF_INET, SOCK_DGRAM)

2.绑定地址

sockfd.bind(addr)

3.消息收发

data, addr = sockfd.recvfrom(buffersize)
# 功能:接收UDP消息
# 参数:每次最多接收多少字节
# 返回值:data	接收到的内容;	addr	消息发送方地址

n = sockfd.sendto(data, addr)
#功能:发送UDP消息
#参数:data	发送的内容(bytes格式);	addr 目标地址
#返回值:发送的字节数

4.关闭套接字

sockfd.close()

服务端代码示例

"""
udp_server.py   udp套接字服务端流程
"""

import socket

# 创建UDP套接字
sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定地址
server_addr = ('127.0.0.1', 8888)
sockfd.bind(server_addr)

# 循环收发消息
while True:
    data, addr = sockfd.recvfrom(1024)
    print("收到消息:", data.decode())
    sockfd.sendto(b'Thanks', addr)

# 关闭套接字
socket.close()

udp客户端代码示例

from socket import *

# 服务器地址
ADDR = ('127.0.0.1', 8888)

# 创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM)

# 循环收发消息
while True:
    data = input("Msg>>")
    if not data:
        break
    # 给服务器发送消息
    sockfd.sendto(data.encode(), ADDR)
    # 接收消息
    msg, addr = sockfd.recvfrom(1024)
    # 打印接收到的消息
    print("From server:", msg.decode())

sockfd.close()

5.5.7 tcp套接字和udp套接字区别

  1. 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
  2. tcp套接字会有粘包,udp套接字有消息边界不会粘包
  3. tcp套接字保证消息的完整性,udp套接字则不能消息的完整性
  4. tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
  5. tcp套接字使用send, recv收发消息,udp套接字使用sendto, recvfrom

5.5.8 socket套接字属性

  1. sockfd.type 套接字类型
  2. sockfd.family 套接字地址类型
  3. sockfd.getsockname() 获取套接字绑定地址
  4. sockfd.fileno() 获取套接字的文件描述符
  5. sockfd.getpeername() 获取连接套接字客户端地址
  6. sockfd.setsockopt(level,option,value)
    ??功能:设置套接字选项
    ??参数:level选项类别 SOL_SOCKET
    ?????option 具体选项内容
    ?????value 选项值
  7. sockfd.getsockopt(level, option)
    ??功能:获取套接字选项值

代码示例

"""
套接字属性介绍
"""

from socket import *

# 创建套接字
s = socket()

# 设置端口可以立即重用
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)    

s.listen(3)

c, addr = s.accept()
print("连接端地址:",c.getpeername())

print("地址类型:",s.family)
print("套接字类型:",s.type)
print("绑定地址:",s.getsockname())
print("文件描述符:",s.fileno())

5.5.8 UDP广播

  1. 广播定义: 一端发送多点接收
  2. 广播地址: 每个网络的最大地址为发送广播的地址,向该地址发送,则网段内所有主机都能接收

代码示例

"""
广播接收	broadcast_recv.py
	1.创建udp套接字
	2.设置套接字可以发送接收广播 (setsockopt)
	3.选择接收的端口
	4.接收广播
"""

from socket import *

s = socket(AF_INET, SOCK_DGRAM)

# 设置套接字接收广播
s.setsockopt(SOL_SOCKET, SO_BROADCAST, True)  # 设置套接字可以接收广播

s.bind(('0.0.0.0', 9999))

msg, addr = s.recvfrom(1024)
print(msg.decode())
"""
广播发送	broadcast_send.py
	1.创建udp套接字
	2.设置可以发送广播
	3.循环向广播地址发送
"""

from socket import *
from time import sleep

# 广播地址 ifconfig的地址
dest = ('192.168.0.161', 9999)

# 创建数据报套接字
s = socket(AF_INET, SOCK_DGRAM)

# 设置可以发送广播
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)

data = """
    ******************
    北京  11.17   初冬
    温度:7
    状态:没有四块五的妞
    ******************
"""

sleep(2)  # 每隔两秒发送一次
s.sendto(data.encode(), dest)  # 目标地址就是广播地址

5.5.9 TCP套接字之HTTP传输

1. HTTP协议(超文本传输协议)
  1. 用途:网页获取,数据的传输
  2. 特点
    ??应用层协议,传输层使用tcp传输
    ??简单,灵活,很多语言都有HTTP专门接口
    ??无状态,协议不记录传输内容
    ??http1.1支持持久连接,丰富了请求类型
2. HTTP请求(request)
  • 请求行:具体的请求类别和请求内容

?GET ?????/ ?????HTTP/1.1
请求类别 ??请求内容 ??协议版本

  • 请求头:对请求的进一步解释和描述,组成格式是一个个的键值对,每个键值对占一行

Accept-Encoding: gzip

  • 空行
  • 请求体:请求参数或提交内容
3. HTTP响应(response)
  • 响应行:反馈基本的响应情况

HTTP/1.1 ???200????OK
版本信息???响应码??附加信息

  • 响应头:对响应内容的描述

Content-Type: text/html

  • 空行
  • 响应体:响应的主体内容信息

http请求和响应的代码示例

"""
httpserver v1.0
基本要求:
    获取来自浏览器的请求
    判断如果请求内容是/,将index.html响应给客户端
    如果返回的是其他内容则响应404
"""

from socket import *


# 客户端(浏览器)处理
def request(connfd):
    # 获取请求,将请求内容提取出来

    data = connfd.recv(4096)  # 接收请求
    if not data:  # 防止浏览器异常退出
        return
    # print(data.decode(), type(data.decode()))

    request_line = data.decode().split('\n')[0]  # 获取请求行
    # print(request_line, type(request_line))   # 打印请求行

    info = request_line.split(" ")[1]  # 获取请求内容
    # print(info)  # 打印请求内容

    # 判断是/则返回index.html,不是/则返回404
    if info == "/":  # 是/则返回index.html
        with open('index.html') as f:  # 用with打开index.html文件
            response = "HTTP/1.1 200 OK\r\n"  # 响应行
            response += "Content-Type:text/html\r\n"  # 响应头(可以不写)
            response += "\r\n"  # 空行
            response += f.read()  # 响应体
    else:  # 不是/则返回404
        response = "HTTP/1.1 404 Not Found\r\n"  # 响应行
        response += "Content-Type:text/html\r\n"  # 响应头(可以不写)
        response += "\r\n"  # 空行
        response += "<h1>Sorry...</h1>"  # 响应体

    # 把响应内容发送给浏览器
    connfd.send(response.encode())


# 搭建tcp网络
sockfd = socket()

# 设置端口可以立即使用
sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)

# 绑定地址
sockfd.bind(('0.0.0.0', 8000))

# 把套接字设置为监听套接字
sockfd.listen(3)

while True:
    connfd, addr = sockfd.accept()
    request(connfd)  # 处理客户端请求

5.5.10 struct模块的使用

  1. 原理:将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析
  2. 接口使用
import struct
st = struct.Struct(fmt)
# 功能:生成结构化对象
# 参数:fmt	定制的数据结构


st.pack(v1, v2, v3...)
# 功能:将一组数据按照指定格式打包转换为bytes
# 参数:要打包的数据
# 返回值:bytes字节串


st.unpack(bytes_data)
# 功能:将bytes字节串按照指定的格式解析
# 参数:要解析的字节串
# 返回值:解析后的内容

struct.pack(fmt, v1, v2, v3,...)
struct.unpack(fmt, bytes_data)
# 说明:可以使用struct模块直接调用pack unpack.此时这两个函数第一个参数传入fmt.其他用法功能相同
# 注意:打包时以什么格式(fmt)打包,解析时就以什么格式解析

在这里插入图片描述在这里插入图片描述

在终端演示:

In [1]: import struct

In [2]: st = struct.Struct('i4sf')

In [3]: data = st.pack(1, b'Lily', 1.65)

In [4]: data
Out[4]: b'\x01\x00\x00\x00Lily33\xd3?'

In [5]: st.unpack(data)
Out[5]: (1, b'Lily', 1.649999976158142)

struct模块的练习

使用udp完成

  • 在客户端不断录入学生信息,将其发送到服务端
  • 在服务端将学生信息写入到一个文件中,每个学生信息占一行
  • 信息格式:id(int) name(str) age(int) score(float)
"""
struct_send.py
"""

from socket import *
import struct

# 先定义好数据格式
st = struct.Struct('i32sif')

# udp套接字
s = socket(AF_INET, SOCK_DGRAM)
ADDR = ('127.0.0.1', 8888)

while True:
    print("=====================")
    id = int(input("ID:"))
    name = input("Name:").encode()
    age = int(input("Age:"))
    score = float(input("Score:"))
    # 数据打包发送
    data = st.pack(id, name, age, score)
    s.sendto(data, ADDR)
"""
struct_recv.py
"""

from socket import *
import struct

# 两端定义同样的数据格式
st = struct.Struct('i32sf')

# 创建udp套接字
s = socket(AF_INET, SOCK_DGRAM)

# 绑定
s.bind(('127.0.0.1', 8888))

# 打开文件
f = open('student.txt', 'a')

while True:
    data, addr = s.recvfrom(1024)
    data = st.unpack(data)

    # 写入文件
    info = "%d  %-10s   %d  %.1f\n" % data
    f.write(info)
    f.flush()

6. 网络并发

6.1 多任务编程

  1. 意义: 充分利用计算机多核资源,提高程序的运行效率
  2. 实现方案: 多进程,多线程
  3. 并行与并发
    ??并发:同时处理多个任务,内核在任务间不断的切换达到好像多个任务被同时执行的效果,实际每个时刻只有一个任务占有内核
    ??并行:多个任务利用计算机多核资源在同时执行,此时多个任务间为并行关系

6.2 进程(process)

1.定义:程序在计算机中的一次运行
??程序是一个可执行的文件,是静态的占有磁盘
??进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期

2.系统中如何产生一个进程
??【1】用户空间通过调用程序接口或者命令发起请求
??【2】操作系统接受用户请求,开始创建进程
??【3】操作系统调配计算机资源,确定进程状态等
??【4】操作系统将创建的进程提供给用户使用

3.进程基本概念

??cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上

??PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程

??进程ID(PID):系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复????Linux查看进程ID:ps -aux

??父子进程:系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0或多个子进程。父子进程关系便于进程管理。??查看进程树:pstree

??进程状态(三态):
????就绪态:进程具备执行条件,等待分配CPU资源
????运行态:进程占有CPU时间片正在运行
????等待态:进程暂时停止运行,让出CPU
在这里插入图片描述
??五态(在三态基础上增加新建和终止 ):
????新建:创建一个进程,获取资源的过程
????终止:进程结束,释放资源的过程
在这里插入图片描述

??状态查看命令: ps -aux -->STAT列
????S?等待态
????R?运行态
????D?等待态
????T?等待态
????Z?僵尸

????<?有较高优先级
????N?优先级较低
????+?前台进程
????会话组组长
????有多线程的

2. 进程的运行特征
??【1】进程可以使用计算机多核资源
??【2】进程是计算机分配资源的最小单位
??【3】进程之间的运行互不影响,各自独立
??【4】每个进程拥有独立的空间,各自使用自己空间资源

6.3 基于fork的多进程编程

"""fork创建进程演示"""

import os

pid = os.fork()
# 功能:创建新的进程
# 返回值:整数,如果创建进程失败返回一个负数,如果成功则在原有进程中返回新进程的PID,在新进程中返回0

if pid < 0:
    print("Create process failed")

#子进程执行的部分
elif pid == 0:
    print("The new progress")

# 父进程执行的部分
else:
    print("The old process")
    
    
# 父子进程都会执行
print("Fork test over")

注意:

  • 子进程会复制父进程全部内存空间,从fork下一句开始执行
  • 父子进程各自独立运行,运行顺序不一定
  • 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配
  • 父子进程有各自特有特征比如PID,PCB, 命令集等
  • 父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响

6.4 进程相关函数

os.getpid()
# 功能:获取一个进程的PID值
# 返回值:返回当前进程的PID

os.getppid()
# 功能:获取父进程的PID
# 返回值:返回父进程的PID

os._exit(status)
# 功能:结束一个进程
# 参数:进程的终止状态

sys.exit([status])
# 功能:退出进程
# 参数:整数 表示退出状态
# 如果参数传的是字符串:表示退出时打印内容

代码示例

import os, sys

pid = os.fork()

if pid < 0:
    print("Error")
elif pid == 0:
    print("Child PID:", os.getpid())  # 子PID
    print("Parent PID:", os.getppid())  # 父PID
else:
    print("Get Child PID:", pid)  # 子PID
    print("Parent PID:", os.getpid())  # 父PID

os._exit(0)  # 退出进程,后面的语句不会再执行
sys.exit("退出")  # 退出进程,并把传的字符串打印

print("Exit")  # 不会执行

6.5 孤儿和僵尸

1.孤儿进程: 父进程先于子进程退出,此时子进程成为孤儿进程
特点:孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理

2.僵尸进程: 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程
特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源

3.如何避免僵尸进程产生

  1. 使用wait函数处理子进程退出
pid, status = os.wait()
# 功能:在父进程中阻塞等待处理子进程退出
# 返回值:pid 退出的子进程的PID;status	子进程退出状态

代码示例

import os, sys

pid = os.fork()
if pid < 0:
    print("Error")
elif pid == 0:
    print("Child PID:", os.getpid())
    sys.exit("子进程退出")
else:
    """
    os.wait() 处理僵尸进程
    """
    pid, status = os.wait()
    print("pid:", pid)
    print("status:", status)  # 打印的status是子进程的退出状态乘以256

    while True:  # 让父进程不退出
        pass
  1. 创建二级子进程处理僵尸
    ??父进程创建子进程,等待回收子进程
    ??子进程创建二级子进程然后退出
    ??二级子进程称为孤儿,和一级子进程的父进程一同执行事件

  2. 通过信号处理子进程退出
    ??原理: 子进程退出时会发送信号给父进程。如果父进程忽略子进程信号,则系统就会自动处理子进程退出
    ??方法: 使用signal模块在父进程创建子进程前写如下语句

import signal
signal.signal(signal.SIGCHLD,signal.SIG_IGN)

???? 代码示例

"""信号处理僵尸"""
import os, sys
import signal

# 子进程退出时父进程忽略退出行为,子进程由系统处理
signal.signal((signal.SIGCHLD, signal.SIG_IGN))

pid = os.fork()
if pid < 0:
    print("Error")
elif pid == 0:
    print("Child PID:", os.getpid())
    sys.exit(2)
else:
    while True:  # 父进程不退出
        pass

6.6 群聊聊天室

需求:

  1. 有人进入聊天室需要输入姓名,姓名不能重复
  2. 有人进入聊天室,其他人会收到通知: XXX进入了聊天室
  3. 一个人发消息,其他人会收到: XXX:xxxxxxxxxxxxxxxx
  4. 有人退出聊天室,其他人也会收到通知: XXX退出了聊天室
  5. 扩展功能:服务器可以向所有用户发送公告:管理员消息:xxxxx

代码实现

客户端代码

"""chat_client.py"""
import os
import sys
from socket import *

# 服务器地址
ADDR = ('127.0.0.1', 8888)


# 发送消息
def send_msg(s, name):
    while True:
        try:
            text = input("发言:")
        except KeyboardInterrupt:
            text = 'quit'
        if text.strip() == 'quit':
            msg = 'Q' + ' ' + name
            s.sendto(msg.encode(), ADDR)
            sys.exit("退出聊天室")
        msg = 'C %s %s' % (name, text)
        s.sendto(msg.encode(), ADDR)


# 接收消息
def recv_msg(s):
    while True:
        try:
            data, addr = s.recvfrom(4096)
        except KeyboardInterrupt:
            sys.exit()
        if data.decode() == 'EXIT':
            sys.exit()
        print(data.decode() + '\n发言:', end='')


# 客户端启动函数
def main():
    s = socket(AF_INET, SOCK_DGRAM)

    # 进入聊天室
    while True:
        name = input("请输入姓名:")
        msg = 'L' + ' ' + name
        s.sendto(msg.encode(), ADDR)
        # 接收服务器的反馈
        data, addr = s.recvfrom(128)
        if data.decode() == 'OK':
            print("您已进入聊天室")
            break
        else:
            print(data.decode())
    # 退出循环表示已经进入聊天室
    pid = os.fork()
    if pid < 0:
        sys.exit("Error!")
    elif pid == 0:
        send_msg(s, name)  # 子进程发消息
    else:
        recv_msg(s)  # 父进程收消息


main()

服务端代码

"""chat_server"""

from socket import *
import os, sys

# 服务器地址
ADDR = ('0.0.0.0', 8888)

# 存储用户信息
user = {}


# 登录
def do_login(s, name, addr):
    if name in user:
        s.sendto("该用户已存在".encode(), addr)
        return
    s.sendto(b'OK', addr)
    # 通知其他人
    msg = "\n欢迎%s进入聊天室" % name
    for i in user:
        s.sendto(msg.encode(), user[i])
    user[name] = addr


# 聊天
def do_chat(s, name, text):
    msg = "\n%s: %s" % (name, text)
    for i in user:
        if i != name:
            s.sendto(msg.encode(), user[i])


# 退出
def do_quit(s, name):
    msg = "\n%s 退出聊天室" % name
    for i in user:
        if i != name:
            s.sendto(msg.encode(), user[i])
        else:
            s.sendto(b'EXIT', user[i])
    del user[name]


# 处理请求
def do_request(s):
    while True:
        data, addr = s.recvfrom(1024)
        temp = data.decode().split(' ')
        if temp[0] == 'L':
            do_login(s, temp[1], addr)
        elif temp[0] == 'C':
            text = ' '.join(temp[2:])
            do_chat(s, temp[1], text)
        elif temp[0] == 'Q':
            do_quit(s, temp[1])


# 搭建网络
def main():
    # udp服务端
    s = socket(AF_INET, SOCK_DGRAM)
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)
    s.bind(ADDR)

    pid = os.fork()
    if pid == 0:
        while True:
            msg = input("管理员消息:")
            msg = "C 管理员 " + msg
            s.sendto(msg.encode(), ADDR)

    # 请求处理函数
    do_request(s)


main()

6.7 multiprocessing模块创建进程

6.7.1 流程特点

  1. 将需要子进程执行的事件封装为函数
  2. 通过模块的Process类创建进程对象,关联函数
  3. 可以通过进程对象设置进程信息及属性
  4. 通过进程对象调用start启动进程
  5. 通过进程对象调用join回收进程

6.7.2 基本接口使用

在这里插入图片描述
注意:

  • 启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建
    在这里插入图片描述
    注意:
  1. 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响
  2. 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容
  3. multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成
  4. multiprocessing创建的子进程中无法使用标准输入(在子进程中无法使用input)

6.7.3 multiprocessing模块创建进程

"""
multiprocessing 模块创建进程
1.编写进程函数
2.生成进程对象
3.启动进程
4.回收进程
"""

import multiprocessing as mp
from time import sleep


# 创建进程函数
def fun():
    print("开始一个进程")
    sleep(5)
    print("子进程结束")


# 创建进程对象
p = mp.Process(target=fun)  # p这个对象就可以代表一个进程了
p.start()  # 启动进程

# 父进程事件
sleep(3)
print("父进程执行的部分写在start和join间")

p.join()  # 回收进程

6.7.4 multiprocessing模块创建多个进程

"""
multiprocessing 创建多个进程
"""
from multiprocessing import Process
from time import sleep
import os


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


def th2():
    sleep(2)
    print("睡觉")
    print(os.getppid(), '--', os.getpid())


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


things = [th1, th2, th3]
jobs = []

for th in things:
    p = Process(target=th)
    jobs.append(p)  # 通过列表保存进程对象
    p.start()

# 一起回收所有子进程
for i in jobs:
    i.join()

上面代码运行结果
在这里插入图片描述

6.7.5 给进程函数传参

"""
Process 给进程函数传参
"""

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, 'Baron'))  # 按照位置传参:2传给sec,Baron传给name
# p = Process(target=worker, kwargs={'sec': 2, 'name': 'Baron'})  # 按照关键字传参
p = Process(target=worker, args=(2,), kwargs={'name': 'Baron'})

p.start()
p.join()

6.7.6 进程对象属性

  • p.name 进程名称
  • p.pid 对应子进程的PID号
  • p.is_alive() 查看子进程是否在生命周期
  • p.daemon 设置父子进程的退出关系
    ??如果设置为True则子进程会随父进程的退出而结束
    ??要求必须在start前设置
    ??如果daemon设置成True通常就不会使用join()
"""
进程对象属性
"""
import time
from multiprocessing import Process


def tm():
    for i in range(3):
        time.sleep(2)
        print(time.ctime())


p = Process(target=tm, name="pname")

p.daemon = True  # 当父进程退出时子进程也退出

p.start()
print("进程名称:", p.name)  # 默认名称Process-1,赋值后是pname
print("进程ID:", p.pid)  # 获取对应子进程的PID
print("is alive:", p.is_alive())  # 查看进程是否在生命周期,True在生命周期False不在生命周期

6.8 进程池

6.8.1 必要性

??进程的创建和销毁过程消耗的资源较多,当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大,进程池技术很好的解决了以上问题

6.8.2 原理

??创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗

6.8.3 进程池的实现

  1. 创建进程池对象,放入适当的进程
from multiprocessing import Pool
Pool(processes)
# 功能:创建进程池对象
# 参数:指定进程数量,默认根据系统自动判定
  1. 将事件加入进程池队列执行
pool.apply_async(func,args,kwargs)
# 功能:使用进程池执行func事件
# 参数:func事件函数
# 参数:args  元组  给func按位置传参
# 参数:kwargs  字典  给func按关键字传参
# 返回值:返回函数事件对象
  1. 关闭进程池
pool.close()
# 功能:关闭进程池
  1. 回收进程池中进程
pool.join()
# 功能:回收进程池中进程

6.8.4 代码示例

"""
进程池使用示例
"""

from multiprocessing import Pool
from time import ctime, sleep


# 进程池事件
def worker(msg):
    sleep(2)
    print(ctime(), '--', msg)


# 创建进程池
pool = Pool(4)  # 如果不写参数,则进程池中进程的数量默认根据系统判定

# 向进程池队列添加事件
for i in range(10):
    msg = "Tedu %d" % i
    pool.apply_async(func=worker, args=(msg,))

# 关闭进程池
pool.close()

# 回收进程池
pool.join()

运行结果
在这里插入图片描述

6.9 进程间通信(IPC)

1.必要性
??进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信
2.常用进程间通信方法
??管道 ?消息队列 ?共享内存? 信号? 信号量 ?套接字

6.9.1 管道通信

  1. 通信原理
    ??在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信
  2. 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex=True)
# 功能:创建管道
# 参数:默认表示双向管道,如果False 表示单向管道
# 返回值:表示管道两端的读写对象
        #如果是双向表示均可读写
        #如果是单向管道fd1只读 fd2只写

fd.recv()
# 功能:从管道获取内容

fd.send(data)
# 功能:向管道写入内容
# 参数:要写入的数据 
  1. 代码示例
"""
pipe.py 管道通信
注意:
    1.multiprocessing中管道通信只能用于有亲缘关系的进程中
    2.管道对象在父进程中创建,子进程通过父进程获取
"""

from multiprocessing import Process, Pipe

# 创建管道
fd1, fd2 = Pipe()


def app1():
    print("启动app1,请登录")
    print("请求app2授权")
    fd1.send("app1 请求登录")  # 写入管道
    data = fd1.recv()
    if data:
        print("登录成功:", data)


def app2():
    data = fd2.recv()  # 阻塞等待读取管道内容
    print(data)
    fd2.send(('Dave', '123'))  # 可以发送任意类型Python类型数据,这里发送了元组


p1 = Process(target=app1)
p2 = Process(target=app2)
p1.start()
p2.start()
p1.join()
p2.join()

运行截图
在这里插入图片描述

6.9.2 消息队列

  1. 通信原理
    ??在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信
  2. 实现方法
from multiprocessing import Queue

q = Queue(maxsize=0)
# 功能:创建队列对象
# 参数:最多存放消息个数
# 返回值:队列对象

q.put(data,[block,timeout])
# 功能:向队列存入消息
# 参数:data 要存入的内容 block 设置是否阻塞(False为非阻塞) timeout 超时检测

q.get([block,timeout])
# 功能:从队列取出消息
# 参数:block 设置是否阻塞(False为非阻塞) timeout 超时检测
# 返回值:返回获取到的内容

q.full()    #判断队列是否为满
q.empty()   #判断队列是否为空
q.qsize()   #判断队列中消息个数
q.close()   #关闭队列 
  1. 代码示例
"""
queue_0.py  消息队列演示
注意:
    消息队列符合先进先出原则
"""

from multiprocessing import Queue, Process
from time import sleep
from random import randint

# 创建消息队列
q = Queue(5)  # 最多存放5个消息


def handle():
    for i in range(6):
        x = randint(1, 33)
        q.put(x)  # 消息入队
    q.put(randint(1, 16))


def request():
    list_ = []
    for i in range(6):
        list_.append(q.get())
    list_.sort()
    list_.append(q.get())
    print(list_)


p1 = Process(target=handle)
p2 = Process(target=request)
p1.start()
p2.start()
p1.join()
p2.join()

6.9.3 共享内存

  1. 通信原理
    ??在内存中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前的内容
  2. 实现方法
    在这里插入图片描述
from multiprocessing import Value, Array

obj = Value(ctype, data)
# 功能:开辟共享内存
# 参数:ctype 表示共享内存空间类型 'i'   'f'   'c'
      # data 共享内存空间初始数据  
# 返回值:共享内存对象

obj.value  # 对该属性的修改查看即对共享内存读写

obj.Array(ctype, data)
# 功能:开辟共享内存空间
# 参数:ctype 表示共享内存数据类型
      # data 整数则表示开辟空间的大小
# 返回值:共享内存对象
"""
value.py    开辟单一共享内存空间
注意:共享内存只能有一个值
"""

from multiprocessing import Process, Value
import time
import random

# 创建共享内存
money = Value('i', 5000)


# 操作共享内存
def man():
    for i in range(30):
        time.sleep(0.2)
        money.value += random.randint(1, 1000)


def girl():
    for i in range(30):
        time.sleep(0.15)
        money.value -= random.randint(100, 800)


p1 = Process(target=man)
p2 = Process(target=girl)
p1.start()
p2.start()
p1.join()
p2.join()

# 获取共享内存
print("一个月余额", money.value)
"""
array.py    共享内存存放一组数据
"""

from multiprocessing import Process, Array

# 创建共享内存
# shm = Array('i', [1, 2, 3, 4])
# shm = Array('i', 5)  # 初始开辟5个整形空间
shm = Array('c', b'hello')


def func():
    # array创建的共享内存对象可迭代
    for i in shm:
        print(i)


p = Process(target=func)
p.start()
p.join()

6.9.4 信号量

  1. 通信原理
    ??给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。
  2. 实现方法
from multiprocessing import Semaphore

sem = Semaphore(num)
# 功能:创建信号量对象
# 参数:信号量的初始值
# 返回值:信号量对象

sem.acquire()	# 将信号量减1 当信号量为0时阻塞
sem.release()	# 将信号量加1
sem.get_value()	# 获取信号量数量
  1. 代码示例
"""
sem.py  信号量演示
思路:信号量数量相当于资源,执行任务必须消耗资源
"""

from multiprocessing import Semaphore, Process
from time import sleep
import os

# 创建信号量(最多允许3个任务同时执行)
sem = Semaphore(3)


# 任务函数
def handle():
    sem.acquire()  # 想执行必须消耗一个信号量
    print("%s 执行任务" % os.getpid())
    sleep(2)
    print("%s 执行任务完毕" % os.getpid())
    sem.release()  # 归还信号量


# 10个任务需要执行
for i in range(10):
    p = Process(target=handle)
    p.start()

7. 线程编程

7.1 线程基本概念

7.1.1 什么是线程

  1. 线程被称为轻量级的进程
  2. 线程也可以使用计算机多核资源,是多任务编程方式
  3. 线程是系统分配内核的最小单元
  4. 线程可以理解为进程的分支任务

7.1.2 线程特征

  1. 一个进程中可以包含多个线程
  2. 线程也是一个运行行为,消耗计算机资源
  3. 一个进程中的所有线程共享这个进程的资源
  4. 多个线程之间的运行互不影响各自运行
  5. 线程的创建和销毁消耗资源远小于进程
  6. 各个线程也有自己的ID等特征

7.2 threading模块创建线程

  1. 创建线程对象
from threading import Thread

t = Thread()
# 功能:创建线程对象
# 参数:target 绑定线程函数
	 # args 元组 给线程函数位置传参
	 # kwargs 字典 给线程函数关键值传参
  1. 启动线程
t.start()
  1. 回收线程
t.join([timeout])

代码示例

"""
thread.py   线程基本使用
步骤:
    1.封装线程函数
    2.创建线程对象
    3.启动线程
    4.回收线程
"""

import threading
from time import sleep
import os


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


# 创建线程对象
t = threading.Thread(target=music)
# 启动线程
t.start()

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

# 回收线程
t.join()
"""
thread  线程函数参数演示
"""

from threading import Thread
from time import sleep


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


# 创建镀多个线程
jobs = []
for i in range(5):
    t = Thread(target=func, args=(2,), kwargs={'name': 'T%d' % i})
    jobs.append(t)
    t.start()

for i in jobs:
    i.join()

7.3 线程对象属性

  • t.name 线程名称
  • t.setName() 设置线程名称
  • t.getName() 获取线程名称
  • t.is_alive() 查看线程是否在生命周期
  • t.daemon 设置主线程和分支线程的退出关系
  • t.setDaemon() 设置daemon属性值
  • t.isDaemon() 查看daemon属性值
    daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用
    代码示例
"""
线程属性
"""

from threading import Thread
from time import sleep


def func():
    sleep(3)
    print("线程属性测试")


t = Thread(target=func, name='よう')

t.setDaemon(True)  # 主线程退出分支线程也退出

t.start()

print("name:", t.getName())

t.setName('Hello')
print("name:", t.getName())

print("is alive:", t.is_alive())

print("daemon:", t.isDaemon())  # 打印daemon的值

7.4 自定义线程类

7.4.1 创建步骤

  1. 继承Thread类
  2. 重写__init__方法添加自己的属性,使用super加载父类属性
  3. 重写run方法

7.4.2 使用方法

  1. 实例化对象
  2. 调用start自动执行run方法
  3. 调用join回收线程

代码示例

"""
自定义线程类示例
"""

from threading import Thread


# 自定义线程类
class ThreadClass(Thread):
    # 重写父类init
    def __init__(self, *args, **kwargs):
        self.attr = args[0]
        super().__init__()  # 加载父类init

    # 假设需要很多步完成步骤
    def f1(self):
        print("step1")

    def f2(self):
        print("step2")

    # 重写run 逻辑调用
    def run(self):
        self.f1()
        self.f2()


t = ThreadClass('abc')
t.start()
t.join()
from threading import Thread
from time import sleep, ctime


class MyThread(Thread):
    def __init__(self, target=None, args=(), kwargs=None):
        super().__init__()  # 此行不许传参
        self.target = target
        self.args = args
        self.kwargs = kwargs

    def run(self):
        self.target(*self.args, **self.kwargs)


def player(sec, song):
    for i in range(3):
        print("Playing %s : %s" % (song, ctime()))
        sleep(sec)


t = MyThread(target=player, args=(3,), kwargs={'song': '凉凉'})
t.start()
t.join()

7.5 线程间通信

7.5.1 通信方法

线程间使用全局变量进行通信

7.5.2 共享资源争夺

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

7.5.3 同步互斥机制

同步: 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
在这里插入图片描述

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

7.5.4 线程同步互斥方法

  1. 线程Event
from threading import Event

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

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

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

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

e.is_set()	# 查看当前e是否被设置

在这里插入图片描述

  1. 线程锁
from threading import Lock

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

with lock:  # 上锁
    ...
    ...

# with代码块结束后自动解锁    

代码示例

"""
thread_lock.py  线程锁
"""

from threading import Thread, Lock

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


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:
    with lock:
        a += 1
        b += 1
        # with语句结束后自动解锁
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:37:09  更:2021-11-23 12:38:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 15:25:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码