线程同步与互斥

目录

资源共享问题

(一)临界资源与临界区

(二)多线程并发访问问题

(三)锁

互斥锁原理

加锁原理

解锁原理

互斥锁相关操作接口

互斥锁封装

死锁

死锁产生的四个必要条件

解决死锁方法

(四)线程同步


资源共享问题

(一)临界资源与临界区

  • 被多线程看到的同一份资源称为临界资源,涉及对临界资源进行操作的上下文代码区域称为临界区
  • 临界资源本质上就是多线程共享资源,而临界区则是涉及共享资源操作的代码区间。

(二)多线程并发访问问题

多线程并发访问问题的核心是多个线程同时访问共享资源,而这些共享资源就是临界资源。涉及对临界资源进行操作的代码区域就是临界区。如果不对临界区进行保护,就会出现竞态条件、数据不一致等问题。

比如存在全局变量 g_val =100 以及两个线程 thread_A 和 thread_B,两个线程同时不断对 g_val 减减。

如果想要对 g_val 进行修改,至少要分为三步:

  1. 先将 g_val 的值拷贝至寄存器中
  2. 在 CPU 内部通过运算寄存器完成计算
  3. 将寄存器中的值拷贝回内存

在 多线程 场景中,存在 线程调度问题,假设此时 A 在执行完第2步后被强行切走了,换成 B 运行:

A 的第3步还没有完成,内存中 g_val 的值还没有被修改,但 A 认为自己已经修改了(完成了第2步),在线程调度时,A 的上下文及相关数据会被保存,A 被切走后,B 会被即刻调度入场,不断执行 g_val -- 操作。

共识:计算机中的硬件,如 CPU 中的寄存器只有一份,被所有线程共享,但其中的内容随线程,不同线程的内容可能不同,也就是我们常说的上下文数据。

假设 B 的运气比较好,进行很多次 g_val -- 操作后都没有被切走:

当 将 g_val 中的值修改为 10 后,就被操作系统切走了,此时轮到 登场,带着自己的之前的上下文数据,继续进行它的未尽事业(完成第3步操作),当然 的上下文数据也会被保存:

这时候就会出现线程安全问题:A把 g_val的值改成了99,

(三)锁

临界资源 要想被安全的访问,就得确保 临界资源使用时的安全性。

对于临界资源访问时的安全问题,可以通过加锁来保证,实现多线程间的 互斥访问,互斥锁就是解决多线程并发访问问题的手段之一。

  • 我们可以 在进入临界区之前加锁,出临界区之后解锁, 这样可以确保并发访问 临界资源 时的绝对串行化,比如之前的 thread_A 和 thread_B 在并发访问 g_val 时,如果进行了加锁,在 thread_A被切走后,thread_B 无法对 g_val 进行操作,因为此时锁被 thread_A 持有,thread_B 只能 阻塞式等待解锁,直到 thread_A 解锁(意味着 thread_A 的整个操作都完成了)。
  • 因此,对于thread_A来说,在 加锁 环境中,只要接手了访问临界资源 g_val 的任务,要么完成、要么不完成,不会出现中间状态,像这种不会出现中间状态、结果可预期的特性称为 原子性 。
  • 加锁 的本质就是为了实现 原子性

注意:

  • 加锁、解锁是比较耗费系统资源的,会在一定程序上降低程序的运行速度。
  • 加锁后的代码是串行化执行的,势必会影响多线程场景中的运行速度。
  • 所以为了尽可能的降低影响,加锁粒度要尽可能的细。

互斥锁原理

加锁原理
lock:movb $0, %alxchgb %al, mutexif(al寄存器里的内容 > 0){return 0;} else挂起等待;goto lock;

解析:

①将 0 赋值给 al 寄存器,这里假设 mutex 默认值为 1(其他不为 0 的整数也行)

movb $0, %al

②将 al 寄存器中的值与 mutex 的值交换(原子操作

xchgb %al, mutex

③判断当前 al 寄存器中的值是否 >0

if(al寄存器里的内容 > 0){return 0;} else挂起等待;

④此时线程 thread_A 就访问 临界区 代码了,如果此时线程 thread_A 被切走了(并没有出临界区,[锁资源] 也没有释放),OS 会保存 thread_A 的上下文数据,并让线程 thread_B 入场:

⑤首先将 al 寄存器中的值赋为 0

movb $0, %al

⑥其次将 al  寄存器中的值与 mutex 的值交换(原子操作

mutex 作为内存中的值,被所有线程共享,因此 thread_B 看到的 mutex 是被 thread_A 修改后的值。

  • 此时的 thread_B 因为没有 [锁资源] 而被拒绝进入 临界区,不止是 thread_B , 后续再多线程(除了 thread_A) 都无法进入 临界区。
  • 而汇编代码中 xchgb %al, mutex 的本质就是 加锁,当 mutex 不为 0 时,表示 钥匙可用,可以进行 加锁;并且因为 xchgb %al, mutex 只有一条汇编指令,足以确保 加锁 过程是 原子性 的

锁的状态由 mutex 决定

  • 如果 mutex == 0,表示锁未被占用,线程可以进入临界区。
  • 如果 mutex != 0,表示锁已被占用,线程需要等待。
解锁原理
unlock:movb $1, mutex唤醒等待 [锁资源] 的线程;return

解析:

①当thread_A走到临界区终点时,进行解锁,将 mutex 中的值赋为 1,

movb $1, mutex

thread_A 都走到了 解锁 这一步,证明它thread_A解锁,已经不需要再访问 临界资源 了,可以让其他线程去访问,也就是 唤醒其他等待 [锁资源] 的线程,然后 return 0 走出 临界区

唤醒等待 [锁资源] 的线程;
return 0;

注意:

  • 加锁是一个让不让你通过的策略
  • 交换指令 swap 或 exchange 是原子的,确保 锁 这个临界资源不会出现问题
  • 未获取到 [锁资源] 的线程会被阻塞至 pthread_mutex_lock() 处

线程在访问临界区前,需要先加锁,所有线程都要看到同一把锁,锁本身也是临界资源 

  •   这种 临界资源 进行了特殊化处理:加锁 和 解锁 操作都是原子的,不存在中间状态,也就不需要保护了。

互斥锁相关操作接口

函数返回值参数备注

//先定义锁

pthread_mutex_t mtx; // 定义一把互斥锁

//后初始化锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

初始化成功返回 0

失败返回 error number

参数1 pthread_mutex_t* 表示想要初始化的锁,这里传的是地址,因为需要在初始化函数中对 互斥锁 进行初始化。

参数2 const pthread_mutexattr_t* 表示初始化时 互斥锁 的相关属性设置,传递 nullptr 使用默认属性。

pthread_mutex_init 初始化 互斥锁 的方式称为 动态分配,需要手动初始化和销毁,除此之外还存在 静态分配,即在定义 互斥锁 时初始化为 

PTHREAD_MUTEX_INITIALIZER

静态分配 的优点在于 无需手动初始化和手动销毁,锁的生命周期伴随程序,缺点就是定义的 互斥锁 必须为 全局互斥锁。

 使用静态分配时,互斥锁必须定义为全局锁。


互斥锁是一种资源,一种线程依赖的资源,因此 [初始化互斥锁] 操作应该在线程创建之前完成,[销毁互斥锁] 操作应该在线程运行结束后执行;总结就是 使用前先创建,使用后需销毁


对于多线程来说,应该让他们看到同一把锁,否则就没有意义


不能重复销毁互斥锁,已经销毁的互斥锁不能再使用。
 

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

//销毁锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

初始化成功返回 0

失败返回 error number

参数 pthread_mutex_t* 表示想要销毁的互斥锁的地址

//加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

初始化成功返回 0

失败返回 error number

参数mutex:表示想要对哪把互斥锁进行加锁

加锁时可能遇到的情况:

当前互斥锁没有被别人持有,正常加锁,函数返回 0

当前互斥锁被别人持有,加锁失败,当前线程被阻塞(执行流被挂起),无法向后运行,直到获得 [锁资源]

//解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

初始化成功返回 0

失败返回 error number

参数mutex:表示想要对哪把互斥锁进行解锁

在 加锁 成功并完成对 临界资源 的访问后,就应该进行 解锁,将 [锁资源] 让出,供其他线程(执行流)进行 加锁。

注意: 如果不进行解锁操作,会导致后续线程无法申请到 [锁资源] 而永久等待,引发 死锁 问题

互斥锁封装

封装思路:利用创建对象时调用构造函数,对象生命周期结束时调用析构函数的特点,融入 加锁、解锁操作

#pragma once#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *pmtx):_pmtx(pmtx){// 加锁pthread_mutex_lock(_pmtx);}~LockGuard(){// 解锁pthread_mutex_unlock(_pmtx);}
private:pthread_mutex_t *_pmtx;
};

死锁

指在一组进程中的各个线程均占有不会释放的资源,但因相互申请被其他线程所占用不会释放的资源处于一种永久等待状态。

  • 例如,设存在两个线程SetThread和GetThread,SetThread持有了资源ObjectA并请求资源ObjectB,而GetThread持有了资源ObjectB并请求资源ObjectA。如果这两个资源的获取是互斥的,并且两个线程都不释放各自持有的资源,那么它们就会无限期地等待对方释放资源,从而形成死锁。
死锁产生的四个必要条件
  1. 互斥:至少有一个资源必须处于非共享状态,即一次只能被一个进程或线程占用。这表示如果一个资源被一个进程占用,那么其他进程就不能使用这个资源,直到第一个进程释放它。
  2. 请求与保持(或称为占有且等待):进程或线程至少需要持有一个资源,并且在等待其他资源时不释放已占有的资源。这表示一个进程或线程在持有至少一个资源的同时,还在请求其他被其他进程或线程持有的资源。
  3. 不可剥夺(或称为非抢占):已分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程主动释放。这意味着资源不能被其他进程或线程强行拿走,除非资源的持有者自愿释放它。
  4. 循环等待:存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。这构成了一个闭环,每个进程或线程都在等待其他某个进程或线程释放资源,从而导致了死锁。
解决死锁方法

方法1:不加锁

不加锁的本质是不保证 互斥,即破坏条件1。


方法2:尝试主动释放锁

比如进入 临界区 访问 临界资源,需要两把锁,thread_A 和 thread_B 各自持有一把锁,并且都在尝试申请第二把锁,但如果此时 thread_A 放弃申请,主动把锁释放,这样就能打破死锁的局面

可以借助 pthread_mutex_trylock 函数实现这种方案

int pthread_mutex_trylock(pthread_mutex_t *mutex);

这个函数就是尝试申请锁,如果长时间申请不到锁,就会把自己当前持有的锁释放,然后放弃加锁,给其他想要加锁的线程一个机会。


方法3:按照顺序申请锁

按照顺序申请锁 -> 按照顺序释放锁 -> 就不会出现环路等待的情况

当需要访问多个资源时,尽量保持一致的加锁顺序。这样可以避免循环等待的情况,因为每个线程或进程都会以相同的顺序请求锁。


方法4:控制线程统一释放锁

锁不一定要由申请锁的线程释放,其他线程也可以释放锁。通常情况下,锁是由申请锁的线程释放的,但特殊机制或框架可能允许其他线程释放锁。

这是由释放锁的机制决定的,直接向 mutex 赋值而非交换,意味着其他线程也能解锁

unlock:movb $1, mutex唤醒等待 [锁资源] 的线程;return

代码演示:

#include<pthread.h>
#include<unistd.h>
#include<iostream>
using namespace std;// 全局互斥锁,无需手动初始化和销毁
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;void *threadRoutine(void *args)
{cout << "我是次线程,开始运行" << endl;// 申请锁pthread_mutex_lock(&mtx);cout << "次线程申请到了一把锁" << endl;// 在不释放锁定情况相下,再次申请锁,陷入 死锁 状态pthread_mutex_lock(&mtx);cout << "次线程又申请到了一把锁" << endl;pthread_mutex_unlock(&mtx);return nullptr;
}int main()
{pthread_t t;pthread_create(&t, nullptr, threadRoutine, nullptr);// 等待次线程先运行3秒sleep(3);cout << "等待3秒..." << endl;// 主线程帮忙释放锁pthread_mutex_unlock(&mtx);cout << "我是主线程,我已帮次线程释放了一把锁" << endl;// 等待次线程后续动作sleep(3);pthread_join(t, nullptr);cout << "线程等待成功" << endl;return 0;
}

演示结果:

因此,我们可以设计一个 控制线程,专门掌管所有的锁资源,如果识别到发生了 死锁 问题,就释放所有的锁,让线程重新竞争。

注意:通常情况下,每个线程或进程只能释放自己持有的锁。如果一个线程持有一个锁,其他线程是无法直接释放这个锁的。这是因为锁通常与特定的线程或进程相关联,以确保资源的独占性和安全性。

(四)线程同步

线程同步是解决饥饿问题的一种方法。饥饿是指在多线程环境中,由于某些线程无法获取必要的资源(如锁、信号量等),导致它们长时间得不到执行的现象。这种情况通常是由于资源分配策略不当或同步机制设计不合理引起的。

原生线程库 中提供了 条件变量 这种方式来实现 线程同步

逻辑链:通过条件变量 -> 实现线程同步 -> 解决饥饿问题

条件变量:当一个线程互斥的访问某个变量时,它可能发现在其他线程改变状态之前,什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中这个线程才被唤醒,这种情况就需要用到条件变量。

其本质就是:衡量访问资源的状态。一旦共享资源的状态发生变化,使得等待的线程或进程可以安全地访问它时,条件变量就会被触发,唤醒等待的线程或进程。这样,线程或进程就可以继续执行,并安全地访问共享资源。

竞态条件:两个或更多进程或线程在并发执行时,其最终的结果依赖于这些进程或线程执行的精确时序。当程序的运行结果因执行顺序的改变而受到影响时,就发生了竞态条件。竞态条件可能会导致超出预期的情况,因此在编程中通常需要避免这种情况。

可以把 条件变量 看作一个结构体,其中包含一个 队列 结构,用来存储正在排队等候的线程信息,当条件满足时,就会取 队头 线程进行操作,操作完成后重新进入 队尾

线程同步相关接口

函数返回值参数备注

//定义1

pthread_cond_t cond; // 定义一个条件变量

//初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

//定义2

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 

成功返回 0

失败返回 error number

参数1 pthread_cond_t* 表示想要初始化的条件变量

参数2 const pthread_condattr_t* 表示初始化时的相关属性,设置为 nullptr 表示使用默认属性

同互斥锁一样,条件变量支持静态分配,即在创建全局条件变量时,定义为 PTHREAD_COND_INITIALIZER,表示自动初始化、自动销毁

这种定义方式只支持全局条件变量。

int pthread_cond_destroy(pthread_cond_t *cond);

成功返回 0

失败返回 error number

参数pthread_cond_t* 表示想销毁的条件变量

//条件等待

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

成功返回 0

失败返回 error number

参数1 pthread_cond_t* 想要加入等待的条件变量。

参数2 pthread_cond_t* 互斥锁,用于辅助条件变量。

条件变量是需要配合互斥锁使用的,需要在获取 [锁资源] 之后,在通过条件变量判断条件是否满足。

传递互斥锁的理由:

条件变量也是临界资源,需要保护。


当条件不满足时(没有被唤醒),当前持有锁的线程就会被挂起,其他线程还在等待锁资源呢,为了避免死锁问题,条件变量需要具备自动释放锁的能力。


当某个线程被唤醒时,条件变量释放锁,该线程会获取锁资源,并进入条件等待状态。
 

//条件唤醒

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

成功返回 0

失败返回 error number

参数pthread_cond_t* 表示想要从哪个条件变量中唤醒线程使用 pthread_cond_signal 一次只会唤醒一个线程,即队头线程。如果想唤醒全部线程,可以使用 pthread_cond_broadcast 。broadcast 就是广播的意思,也就是挨个通知该 条件变量 中的所有线程访问 临界资源

代码演示:

#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;// 互斥锁和条件变量都定义为自动初始化和释放
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int num = 5; // 五个线程void *Active(void *args)
{const char *name = static_cast<const char*>(args);while(true){// 加锁pthread_mutex_lock(&mtx);// 等待条件满足pthread_cond_wait(&cond, &mtx);cout << "thread " << name << " 正在运行" << endl;// 解锁pthread_mutex_unlock(&mtx);}delete[] name;return nullptr;
}int main()
{pthread_t pt[num];for(int i = 0; i < num; i++){char *name = new char[32];snprintf(name, 32, "thread-%d", i);pthread_create(pt+i, nullptr, Active, (void*)name);}// 等待所以次线程就位sleep(3);// 主线程唤醒次线程while(true){cout << "Main thread wake up another thread..." << endl;pthread_cond_signal(&cond); // 单个// pthread_cond_broadcast(&cond); // 广播sleep(1);}for(int i = 0; i < num; i++)pthread_join(pt[i], nullptr);return 0;
}

单个唤醒演示结果:

广播唤醒演示结果:

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC 基本概念与代码示例

1. SpringMVC 简介 SpringMVC 是 Spring 框架中的一个 Web 层框架&#xff0c;基于 MVC&#xff08;Model-View-Controller&#xff09; 设计模式&#xff0c;提供了清晰的分层结构&#xff0c;适用于 Web 应用开发 SpringMVC 主要组件 DispatcherServlet&#xff08;前端控…

Banana Pi OpenWRT One Wifi6 OpenWrt社区官方开源路由器评测

第一款不可破解、开源、版权软件、符合 FCC、CE 和 RoHS 的维修权路由器 OpenWRT项目今年已经20岁了&#xff0c;为了纪念这一时刻&#xff0c;Banana Pi OpenWrt One/AP-24.XY路由器开发系统已经上市。这是OpenWRT团队与硬件公司的第一个联合项目。选择 Banana Pi&#xff0c;…

打造智能钉钉机器人:借助智谱GLM-4-Flash实现高效智能回复(文末附源码)

文章目录 前言一、准备工作&#xff08;一&#xff09;钉钉机器人&#xff08;二&#xff09;智谱 GLM-4-Flash&#xff08;三&#xff09;内网穿透工具 cpolar&#xff08;四&#xff09;需要准备的工具和环境 二、钉钉机器人的创建与配置步骤1&#xff1a;创建钉钉机器人步骤…

react基础语法视图层类组件

react基础语法视图层&类组件 MVVM *区别mvc&mvvm 两者的区别&#xff1a; 数据模型去渲染视图。数据层改了&#xff0c;vue自己会监听到帮我们拿最新的数据去渲染视图&#xff1b;构建数据构建视图&#xff0c;数据驱动的思想。这一套是非常相似的。 视图中的内容改变&…

数据结构--【顺序表与链表】笔记

顺序表 template <class T> class arrList :public List<T> //表示 arrList 类以公有继承的方式继承自 List<T> 类 //公有继承意味着 List<T> 类的公共成员在 arrList 类中仍然是公共成员&#xff0c;受保护成员在 arrList 类中仍然是受保护成员。 { …

Docker容器与宿主机目录映射深度解析

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Docker容器与宿主机目录映射基础原理 在深入了解如何查询 Docker 容器目录在宿主机的映射目录之前&#xff0c;有必要先明晰其背后的基础原理。Docker 容器通过挂载&#xff08;mount&#xff09;机制将宿主机的…

p5.js:模拟 n个彩色小球在一个3D大球体内部弹跳

向 豆包 提问&#xff1a;编写一个 p5.js 脚本&#xff0c;模拟 42 个彩色小球在一个3D大球体内部弹跳。每个小球都应留下一条逐渐消失的轨迹。大球体应缓慢旋转&#xff0c;并显示透明的轮廓线。请确保实现适当的碰撞检测&#xff0c;使小球保持在球体内部。 cd p5-demo copy…

javascript-es6 (六)

编程思想 面向过程 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次 调用就可以了 就是按照我们分析好了的步骤&#xff0c;按照步骤解决问题 面向对象 面向对象是把事务分解成为一个个对象&…

Linux第0节:Linux环境的搭建

一、Linux 环境的搭建方式 搭建方式主要有三种&#xff1a; 直接安装在物理机上。但是由于 Linux 桌面使用起来非常不友好&#xff08;不推荐&#xff09;。使用虚拟机软件, 将 Linux 搭建在虚拟机上。但是由于当前的虚拟机软件(如 VMWare 之类的)存在一些 bug , 会导致环境上…

计算机网络:计算机网络的概念

1.计算机网络&#xff1a;由若干个结点和链接这些的链路组成。 2.集线器&#xff08;Hub&#xff09;&#xff1a;可以把多个结点连接起来&#xff0c;组成一个计算机网络。 不能避免数据冲突的情况 3.交换机&#xff08;Switch&#xff09;:可以把多个结点连接起来&#x…

mysql的锁-->一篇读懂所有锁机制

目录 mysql的锁 概述&#xff1a;根据mysql锁的大类型可以分为 我们先来讲一下范围最大的全局锁 使用 为什么要使用全局锁&#xff1f; 使用全局锁进行备份的缺点 表级锁 表锁 1.共享读表锁的语法 2.排斥写表锁 元数据锁 意向锁 什么是意向锁 怎么产生意向锁 意向…

Vue 实现智能检测文字是否溢出,溢出显示省略号,鼠标悬浮显示全部【附封装组件完整代码+详细注释+粘贴即食】

一、场景需求 在项目中&#xff0c;经常会遇到文本内容超出容器的情况。为了提高用户体验&#xff0c;我希望在文字溢出时显示悬浮提示&#xff0c;未溢出时则不显示。 二、效果演示 三、实现原理 DOM宽度对比法&#xff1a;通过比较元素的scrollWidth&#xff08;实际内容宽…

用Deepseek写一个 HTML 和 JavaScript 实现一个简单的飞机游戏

大家好&#xff01;今天我将分享如何使用 HTML 和 JavaScript 编写一个简单的飞机游戏。这个游戏的核心功能包括&#xff1a;控制飞机移动、发射子弹、敌机生成、碰撞检测和得分统计。代码简洁易懂&#xff0c;适合初学者学习和实践。 游戏功能概述 玩家控制&#xff1a;使用键…

《Spring日志整合与注入技术:从入门到精通》

1.Spring与日志框架的整合 1.Spring与日志框架进行整合&#xff0c;日志框架就可以在控制台中&#xff0c;输出Spring框架运行过程中的一些重要的信息。 好处&#xff1a;方便了解Spring框架的运行过程&#xff0c;利于程序的调试。 Spring如何整合日志框架 Spring5.x整合log4j…

关于mybatis查询时,时间字段的映射问题

目录 1.mysql中&#xff0c;关于时间的两种类型 1.1 date 1.2 datetime 2.mybatis从mysql数据库查询出上述两种类型的字段后&#xff0c;映射到Java实体类时的问题 3.结语 1.mysql中&#xff0c;关于时间的两种类型 1.1 date 格式&#xff1a;2002-09-23 特点&#xff1a…

高效自动化测试:打造Python+Requests+Pytest+Allure+YAML的接口测试框架

一、背景 在快节奏的开发周期中&#xff0c;如何确保接口质量&#xff1f;自动化测试是关键。通过构建标准化、可复用的测试框架&#xff0c;能显著提升测试效率与准确性&#xff0c;为项目质量保驾护航[1][7]。 二、目标 ✅ 核心目标&#xff1a; ● 实现快速、高效的接口测试…

【鸿蒙开发】MongoDB入门

https://www.mongodb.com/try/download/community 下载MongoDB: var mongoose require("mongoose");// localhost 域名&#xff0c;代表本机 // 127.0.0.1 ip , 代码本机 mongoose.connect("mongodb://localhost:27017/jiaju").then(() > {console.l…

Linux中的TCP编程接口基本使用

TCP编程接口基本使用 本篇介绍 在UDP编程接口基本使用已经介绍过UDP编程相关的接口&#xff0c;本篇开始介绍TCP编程相关的接口。有了UDP编程的基础&#xff0c;理解TCP相关的接口会更加容易&#xff0c;下面将按照两个方向使用TCP编程接口&#xff1a; 基本使用TCP编程接口…

wireshark 如何关闭混杂模式 wireshark操作

Fiddler和Wireshark都是进行抓包的工具&#xff1a;所谓抓包就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作&#xff0c;也用来检查网络安全。抓包也经常被用来进行数据截取等。黑客常常会用抓包软件获取你非加密的上网数据&#xff0c;然后通过分析&#…

IDEA2024又一坑:连接Docker服务连不上,提示:Cannot run program “docker“: CreateProcess error=2

为新电脑安装了IDEA2024版&#xff0c;因为局域网中安装有Docker,所以这台电脑上没有安装&#xff0c;当运行时发现死活连不上Docker报&#xff1a;Cannot run program “docker“: CreateProcess error2 分析&#xff1a; Docker服务有问题 其它电脑都能连&#xff0c;排除 网…