当前位置: 首页 > news >正文

Linux - POSIX信号量,基于环形队列的生产者消费者模型

信号量

在Linux下,POSIX信号量是一种线程同步机制,用于控制多个线程之间的访问顺序。POSIX信号量可以用于实现线程之间的互斥或者同步。


在之前的阻塞队列生产者消费者模型中,阻塞队列是一个共享资源,不管是生产者还是消费者,任何时刻只允许一个线程访问共享资源,所以对阻塞队列进行了加锁保护,使阻塞队列成为为临界资源。在这里,是将整个共享资源(阻塞队列)当作一个整体使用的。

信号量使用思路:一个共享资源,不当作整体使用,将其划分为若干个小的共享资源,使得不同的执行流访问不同的小共享资源,本质上,当不同执行流访问不同的小共享资源时,不需要进行加锁保护,可以并发执行。而如果两个执行流访问到了同一个小的共享资源时,再进行互斥保护共享资源。


POSIX信号量提供了两种类型的信号量:二进制信号量和计数信号量。二进制信号量只有两个取值:0和1,它用于表示资源是否被占用。计数信号量可以有多个取值,它表示可用资源的数量。

二进制信号量可以充当互斥锁的作用。因为信号量的初始值为1,p相当于加锁,v相当于解锁。

计数信号量的值通常被称为“资源计数器”,它记录了当前可用的共享资源数量。当一个线程需要使用共享资源时,它会尝试获取计数信号量,如果当前可用资源数量大于0,则线程可以获取资源并将计数信号量的值减1。如果当前可用资源数量为0,则表示共享资源已被占用,线程需要等待直到有资源可用。

POSIX信号量接口

       int sem_init(sem_t *sem, int pshared, unsigned int value); // 初始化int sem_destroy(sem_t *sem);    // 销毁int sem_wait(sem_t *sem);       // 申请信号量,P操作int sem_post(sem_t *sem);       // 释放信号量,V操作

因为信号量本质就是一个计数器,记录着当前的资源数目,其中初始化时的value值就表示初始时的资源数目。

在使用POSIX信号量时,线程可以通过调用sem_wait()函数来请求获取信号量,如果当前信号量的值大于0,则线程可以继续执行,同时信号量的值会减1;否则线程将被阻塞,等待信号量的值变为大于0。当线程释放资源时,可以通过调用sem_post()函数来释放信号量,使得其它线程可以获取资源。

基于环形队列的生产者消费者模型

1.缓冲区的改变

基于环形队列的生产者消费者模型,对比之前的基于阻塞队列的生产者消费者模型,只是缓冲区的具体数据结构变了。两个角色:生产者,消费者。三种关系:生产者与生产者:互斥。消费者与消费者:互斥。生产者与消费者:互斥与同步。

如上图,环节队列是逻辑抽象结构,底层的物理结构是数组。要想达到环形队列的效果,只需在访问数组的最后一个元素的下一个元素时将下标 %= size即可。再者,环形队列有一个判空和判满的问题,因为这里的容量是有上限的,从数据结构的角度来说,一般会有一个start和end下标,当start == end时为空,而当数据为满时,不能也将start == end标定为满,因为这样空和满的判断条件就相同了。解决方法:1. 空出一个位置,当(end + 1) % size == start时,判定为满。 2. 加一个计数器,当计数器 == size时为满,计数器 == 0时为空。

但是,因为信号量的作用,在环形队列生产消费模型中,我们不需要考虑判空判满的问题。见下文。

2. 结合生产者消费者

先考虑单生产者,单消费者的环形队列生产消费模型,有以下前提:

  1. 通过给生产者线程和消费者线程分别设定一个下标,从而标定它们当前生产和消费的下标位置。

  1. 我们不需要通过空出一个位置或者添加计数器的方式来避免队列为空和为满时生产消费线程的下标相同这样的判定条件重复。因为有信号量的存在。

  1. 因此,队列为空和为满时,生产者线程的下标一定等于消费者线程的下标。一种情况是当前缓冲区中没有数据,一种是当前缓冲区中数据已满。

目的期望:

  1. 当生产线程和消费线程指向了环形队列的同一个位置时(此时队列为空or为满),也就是访问了同一个共享资源,此时需要进行多线程互斥与同步。

  1. 当生产线程和消费线程不指向环形队列的同一个位置时,此时不需要进行互斥与同步,可以并发执行,因为没有访问同一个共享资源。

  1. 当环形队列为空时,必须让生产者先执行,消费者必须等待生产者生产完至少一个数据。这也就是第一点中的互斥与同步。

  1. 当环形队列为满时,必须让消费者先执行,生产者必须等待消费者消费完至少一个数据。这也就是第一点中的互斥与同步。

  1. 当环形队列不为空不为满时,生产线程与消费线程并发执行。

上方5点,其实345和12是一个原则。34对应1,5对应2。

3.结合信号量

信号量表征的是共享资源的数量,在环形队列生产消费模型中,设定两个信号量,分别表征数据资源数目和空间资源数目。生产者关注空间资源信号量,初始为N。消费者关注数据资源信号量,初始为0。

按照上面生产者和消费者的执行逻辑来看,每次在生产或者消费之前必须先申请信号量,申请信号量的本质是一种对资源的预定机制。若信号量大于0,则--信号量,继续执行。若为0,则阻塞等待信号量被其他线程释放。

这样一来,起始时,或当其他环形队列为空时,dataSem为0,消费者没有数据资源可以申请,故必须阻塞等待。这也就是我们期望的,当生产和消费的下标相等时,且队列为空时,必须让生产者先执行,消费者阻塞等待。

当环形队列为满时,spaceSem为0,dataSem为N,生产者没有空间资源可以申请,故必须阻塞等待。这也就是我们期望的,当生产和消费下标相等时,且队列为满时,必须让消费者先执行,生产线程必须阻塞等待。

(注意,上方两种互斥情况下,只有当另一方生产/消费完,执行完V操作之后,阻塞方才能P成功,这也就实现了互斥和同步

当环形队列不为空时,生产和消费的下标不同,此时并没有访问同一个资源,故可以并发执行。

4.代码...

ringQueue.hpp

#ifndef _RINGQUEUE_HPP_
#define _RINGQUEUE_HPP_#include <vector>
#include "sem.hpp"
#include "mutex.hpp"const int g_default_size = 5;template <class T>
class RingQueue
{
public:RingQueue(int size = g_default_size): ring_queue_(size), size_(size), space_sem_(size), data_sem_(0), p_index_(0), c_index_(0), p_mutex_(), c_mutex_(){}~RingQueue() = default;void push(const T &in){// 生产者: 环形队列的push的操作space_sem_.p(); // 空间资源的p操作p_mutex_.lock();ring_queue_[p_index_++] = in;p_index_ %= size_;p_mutex_.unlock();data_sem_.v(); // 数据资源v操作}void pop(T *out){data_sem_.p(); // 数据资源的p操作,消费者进行。若没有数据资源,则阻塞挂起消费者线程c_mutex_.lock();*out = ring_queue_[c_index_++];c_index_ %= size_;c_mutex_.unlock();space_sem_.v(); // 空间资源v操作}private:std::vector<T> ring_queue_; // 使用vector模拟环形队列int size_;                  // 环形队列的大小int p_index_;   // 生产者下标int c_index_;   // 消费者下标Sem space_sem_; // 空间资源信号量,生产者使用Sem data_sem_;  // 数据资源信号量,消费者使用Mutex p_mutex_;Mutex c_mutex_;
};// 自己写的:环形队列利用互斥锁+条件变量进行。
// template <class T>
// class RingQueue
// {
// public:
//     RingQueue(int size = g_default_size)
//     : ring_queue_(size)
//     , size_(size)
//     , c_index_(0), p_index_(0)
//     , c_mutex_(), p_mutex_()
//     {
//         pthread_cond_init(&not_empty_, nullptr);
//         pthread_cond_init(&not_full_, nullptr);
//     }
//     ~RingQueue()
//     {
//         pthread_cond_destroy(&not_empty_);
//         pthread_cond_destroy(&not_full_);
//     }
//     void push(const T &in)
//     {
//         // 生产者
//         p_mutex_.lock();  // 加锁//         while((p_index_ + 1) % size_ == c_index_)
//         {
//             // 满的
//             pthread_cond_wait(&not_full_, &p_mutex_.mtx_);
//         }
//         // 不满了
//         ring_queue_[p_index_++] = in;
//         p_index_ %= size_;
//         pthread_cond_signal(&not_empty_);//         p_mutex_.unlock(); 
//     }
//     void pop(T *out)
//     {
//         // 消费者
//         c_mutex_.lock();//         while(c_index_ == p_index_)  // 为空,没有数据资源,等待条件变量。
//         {
//             pthread_cond_wait(&not_empty_, &c_mutex_.mtx_);
//         }
//         *out = ring_queue_[c_index_++];
//         c_index_ %= size_;
//         pthread_cond_signal(&not_full_);//         c_mutex_.unlock();
//     }
// private:
//     std::vector<T> ring_queue_;  // 环形队列
//     int size_;
//     int c_index_;
//     int p_index_;
//     Mutex c_mutex_;
//     Mutex p_mutex_;
//     pthread_cond_t not_empty_;
//     pthread_cond_t not_full_;
// };#endif

testMain.cc

#include "ringQueue.hpp"
#include <pthread.h>
#include <iostream>
#include <unistd.h>
using namespace std;// 消费者
void *consumer(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args; // 生产消费的缓冲区:环形队列while (true){sleep(1);int x;rq->pop(&x);cout << "消费:" << x << " [" << pthread_self() << "]" << endl;}pthread_exit(nullptr);
}// 生产者
void *producer(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (true){int x = rand() % 100 + 1;rq->push(x);cout << "生产:" << x << " [" << pthread_self() << "]" << endl;}pthread_exit(nullptr);
}// int main()
// {
//     srand((unsigned int)time(nullptr));//     // RingQueue<int>* ring_queue = new RingQueue<int>();
//     RingQueue<int> ringQueue;
//     pthread_t c[2], p[3];     // 多生产多消费,3生产2消费//     for(int i = 0; i < 2; ++i)
//         pthread_create(c + i, nullptr, consumer, &ringQueue);
//     for(int i = 0; i < 3; ++i)
//         pthread_create(p + i, nullptr, producer, &ringQueue);//     for(int i = 0; i < 2; ++i)
//         pthread_join(c[i], nullptr);
//     for(int i = 0; i < 3; ++i)
//         pthread_join(p[i], nullptr);
//     return 0;
// }
int main()
{srand((unsigned int)time(nullptr));RingQueue<int> ringQueue;pthread_t c, p;pthread_create(&c, nullptr, consumer, &ringQueue);pthread_create(&p, nullptr, producer, &ringQueue);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}// 二元信号量充当互斥锁// int tickets = 10000;
// pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;// void *getTickets(void *args)
// {
//     Sem *sem = (Sem *)args;
//     while (true)
//     {
//         sem->p(); // 申请信号量,原子的,只有一个线程会申请成功
//         // pthread_mutex_lock(&mtx);
//         if (tickets > 0)
//         {
//             usleep(1000); // 1ms,模拟抢票过程
//             cout << "tickets : " << tickets << " [" << pthread_self() << "]" << endl;
//             tickets--;
//             sem->v();
//             // pthread_mutex_unlock(&mtx);
//         }
//         else
//         {
//             sem->v();
//             // pthread_mutex_unlock(&mtx);
//             break;
//         }
//     }
//     pthread_exit(nullptr);
// }// int main()
// {
//     pthread_t tids[4];
//     Sem sem(1);
//     for (int i = 0; i < 4; ++i)
//     {
//         pthread_create(tids + i, nullptr, getTickets, &sem);
//     }//     for (int i = 0; i < 4; ++i)
//     {
//         pthread_join(tids[i], nullptr);
//     }
//     return 0;
// }

sem.hpp

#ifndef _SEM_HPP_
#define _SEM_HPP_#include <semaphore.h>// 信号量的封装
class Sem
{
public:Sem(int value){sem_init(&sem_, 0, value);}~Sem(){sem_destroy(&sem_); }void p(){sem_wait(&sem_);}void v(){sem_post(&sem_);}
private:sem_t sem_;
};#endif

5.多生产多消费

多生产多消费时,任何一个生产者和一个消费者之间都已经通过信号量达到了应有的互斥与同步,现在需要考虑的是生产生产之间和消费消费之间的互斥关系。

生产生产之间,消费消费之间,需要保护的共享资源为下标:c_index_或者p_index_,环形队列:ring_queue_(STL:vector,不是线程安全的),故,需要在push和pop内部,进行加锁,构建临界区,保护临界资源。

因为这里生产和消费之间的互斥,已经在当下标相等时,为空或为满时,由信号量维护了,所以这里用两把锁,分别维护生产生产之间和消费消费之间。(见上方代码实现)

6.基于环形队列的生产消费模型为什么效率高,优势?

我们可以看到,多个生产之间,在对环形队列push时,内部是互斥的,也就是串型执行的。多个消费之间,在对环形队列pop时,内部是互斥的,也是串行执行的。对比阻塞队列的生产消费模型来说,改进就是,当缓冲区,环形队列不为空且不为满时,生产和消费的push和pop可以并发执行。

还是那句话,生产消费的过程并非只是pushpop的过程(放数据和拿数据的过程),还有生产者放数据之前生产数据和消费者拿数据之后处理数据的过程(这里才是最耗时间的)。多生产多消费的意义是,可以让生产数据和处理数据时多个线程并发执行,从而提高效率。 (和基于阻塞队列的一样)

7.信号量的理解

信号量的本质就是一个计数器,表示当前资源数目。计数器的意义是什么呢?

在之前使用互斥锁,条件变量的使用场景下,常规流程是:申请锁->判断临界资源是否就绪->就绪则访问,不就绪则在条件变量下等待->访问临界资源->释放锁。

而信号量这个计数器的意义就是:可以不进入临界区就得知资源情况,减少了临界区内的资源是否就绪的判断。本来,信号量就是一种资源预定机制,当P操作成功时,就代表着此时有一个资源可以供你获取/访问(比如环形队列中的空间资源/数据资源)。

相关文章:

Linux - POSIX信号量,基于环形队列的生产者消费者模型

信号量在Linux下&#xff0c;POSIX信号量是一种线程同步机制&#xff0c;用于控制多个线程之间的访问顺序。POSIX信号量可以用于实现线程之间的互斥或者同步。在之前的阻塞队列生产者消费者模型中&#xff0c;阻塞队列是一个共享资源&#xff0c;不管是生产者还是消费者&#x…...

学习Flask之七、大型应用架构

学习Flask之七、大型应用架构 尽管存放在单一脚本的小型网络应用很方便&#xff0c;但是这种应用不能很好的放大。随着应用变得复杂&#xff0c;维护一个大的源文件会出现问题。不像别的网络应用&#xff0c;Flask没有强制的大型项目组织结构。构建应用的方法完全留给开发者。…...

CentOS9下编译FFMPEG源码

克隆...

炼石:八年饮冰难凉热血,初心如磐百炼成钢

炼石成立八周年 八载笃行&#xff0c;踔厉奋发。创立于2015年的炼石&#xff0c;今天迎来了八岁生日&#xff0c;全体员工共同举行了温暖又充满仪式感的周年庆典。过去的2022&#xff0c;是三年疫情的艰难“收官之年”&#xff0c;新的2023&#xff0c;将是数据安全行业成为独…...

Python基本数据类型

Python有六种基本数据类型Number&#xff08;数字&#xff09;String&#xff08;字符串&#xff09; List&#xff08;列表&#xff09; Tuple&#xff08;元组&#xff09; Set&#xff08;集合&#xff09;Dictionary&#xff08;字典&#xff09;String&#xff08;字符串&…...

【MySQL进阶】 锁

&#x1f60a;&#x1f60a;作者简介&#x1f60a;&#x1f60a; &#xff1a; 大家好&#xff0c;我是南瓜籽&#xff0c;一个在校大二学生&#xff0c;我将会持续分享Java相关知识。 &#x1f389;&#x1f389;个人主页&#x1f389;&#x1f389; &#xff1a; 南瓜籽的主页…...

javascript高级程序设计第四版读书笔记-第五章 基本引用类型

19.如何创建一个指定的本地时间&#xff1f; Dete只能接收时间戳&#xff0c;有两种方法可以将字符串参数变为时间戳,他们是Date隐式调用的&#xff0c; 分别是Date.parse() 创建的是GTM时间&#xff0c;Date.UTC()创建的是本地时间 Date.UTC()方法也返回日期的毫秒表示&#x…...

《爆肝整理》保姆级系列教程python接口自动化(二十一)--unittest简介(详解)

简介 前边的随笔主要介绍的requests模块的有关知识个内容&#xff0c;接下来看一下python的单元测试框架unittest。熟悉 或者了解java 的小伙伴应该都清楚常见的单元测试框架 Junit 和 TestNG&#xff0c;这个招聘的需求上也是经常见到的。python 里面也有单元 测试框架-unitt…...

【C++的OpenCV】第四课-OpenCV图像常用操作(一):Mat对象深化学习、灰度、ROI

我们开始图像处理的基本操作的了解一、图像对象本身的加深学习1.1 Mat对象和ROI1.1.1 创建一个明确的Mat对象1.1.2 感兴趣的区域ROI二、图像的灰度处理2.1 概念2.2 cvtColor()函数2.3 示例一、图像对象本身的加深学习 1.1 Mat对象和ROI 这是一个技术经验的浅尝&#xff0c;所以…...

Propargyl-PEG1-SS-PEG1-PFP ester,1817735-30-0,炔基应用于生物标记

【中文名称】丙炔-单乙二醇-二硫键-单乙二醇-五氟苯酚酯【英文名称】 Propargyl-PEG1-SS-PEG1-PFP ester【结 构 式】【CAS号】1817735-30-0【分子式】C16H15F5O4S2【分子量】430.4【基团部分】炔基基团【纯度标准】95%【包装规格】1g&#xff0c;5g&#xff0c;10g&#xff0c…...

产品运营︱用户活跃度低的解决方法

app用户活跃度低&#xff0c;产品拉新变现效率慢&#xff0c;这是运营app时难免会遇到的情况。要想解决这类问题&#xff0c;就要从可能的原因下手&#xff0c;进行产品的优化改进&#xff0c;记录下改变后的关键数据变化&#xff0c;定期做好复盘工作进行调整。 一、app用户量…...

【华为OD机试模拟题】用 C++ 实现 - 求最大数字

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

吉卜力风格水彩画怎么画?

著名的水彩艺术家陈坚曾说&#xff1a;“水彩是用水润调和形成的饱和度极高的艺术画面&#xff0c;在纸上晕染的画面面积、强度等具有许多随意性&#xff0c;天空的颜色乌云密布&#xff0c;都是很随意的&#xff0c;难以模仿。” 是的&#xff0c;水彩画的妙处就在于不确定的…...

Python的类变量和对象变量声明解析

Python的类变量和对象变量声明解析 原文链接&#xff1a;https://www.cnblogs.com/bwangel23/p/4330268.html Python的类和C一样&#xff0c;也都是存在两种类型的变量&#xff0c;类变量和对象变量&#xff01;前者由类拥有&#xff0c;被所有对象共享&#xff0c;后者由每个…...

#笨鸟先飞 猴博士电路笔记 第一篇 电路基础

第零课 基础知识串联与并联电源电势与电位差第一课 电阻电路的等效变换电压源串联电流源并联电压源和电流源串联电压源和电流源并联电压源转化为电流源电流源转化为电压源Δ-Y等效变换第二课 基尔霍夫定律基尔霍夫电流定律任一结点上流出电流之和等于流入电流之和。受控电流源&…...

快捷式~node.js环境搭建

1、安装包官网下载&#xff1a;Node.js (nodejs.org) 2、安装完成后修改环境变量 在上面已经完成了 node.js 的安装&#xff0c;即使不进行此步骤的环境变量配置也不影响node.js的使用 但是&#xff0c;若不进行环境变量配置&#xff0c;那么在使用命令安装 node.js全局模块 …...

ZooKeeper实现分布式队列、分布式锁和选举详解

提示&#xff1a;本文章非原创&#xff0c;记录一下优秀的干货。 [原创参考]&#xff1a;https://blog.csdn.net/qq_40378034/article/details/117014648 前言 ZooKeeper源码的zookeeper-recipes目录下提供了分布式队列、分布式锁和选举的实现GitHub地址。 本文主要对这几种实…...

【swift】swift quick start

一、常量和变量 常量let&#xff0c;变量var 也可以用于确定数组和字典的不可变和可变 二、数据类型&#xff1a; Int&#xff1a;整数类型&#xff0c;可表示有符号整数或无符号整数&#xff0c;分别使用Int和UInt表示。 Float&#xff1a;单精度浮点数类型&#xff0c;用于…...

浅谈volatile关键字

文章目录1.保证内存可见性2.可见性验证3.原子性验证4.原子性问题解决5.禁止指令重排序6.JMM谈谈你的理解6.1.基本概念6.2.JMM同步规定6.2.1.可见性6.2.2.原子性6.2.3.有序性6.3.Volatile针对指令重排做了啥7.你在哪些地方用过Volatile&#xff1f;volatile是Java提供的轻量级的…...

10 种 Spring事务失效场景

10 种 Spring事务失效场景 1.概述 Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API&#xff0c;实现了一致的编程模型&#xff0c;而Spring的声明式事务功能更是提供了极其方便的事务配置方式&#xff0c;配合Spring Boot的自动…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...