信号量机制解决经典同步互斥问题

生产者 / 消费者问题、读者 / 写者问题和哲学家问题是操作系统的三大经典同步互斥问题。本文将介绍这三个问题的基本特点以及如何用信号量机制进行解决。

在分析这三个问题之前,我们首先需要了解用信号量机制解决同步互斥问题的一般规律: 实现同步与互斥的P、V操作都是成对出现,但互斥问题的P、V操作出现在同一个进程中;同步问题的P、V操作出现在不同进程中。

1. 生产者 / 消费者问题

1.1 基本特点

生产者/消费者问题具体表现为:

  1. 两个进程对同一个内存资源进行操作,一个是生产者,一个是消费者。
  2. 生产者往共享内存资源填充数据,如果区域满,则等待消费者消费数据。
  3. 消费者从共享内存资源取数据,如果区域空,则等待生产者填充数据。
  4. 生产者的填充数据行为和消费者的消费数据行为不可在同一时间发生。

1.2 解决思路

首先,我分析了其中存在的同步互斥关系: 生产者-消费者之间的同步关系表现为缓冲区空,则消费者需要等待生产者往里填充数据,缓冲区满则生产者需要等待消费者消费。两者共同完成数据的转移或传送;生产者-消费者之间的互斥关系表现为生产者往缓冲区里填充数据的时候,消费者无法进行消费,需要等待生产者完成工作,反之亦然。

然后,我根据存在的互斥同步关系设置了三个信号量:由于存在互斥关系,我设置了一个互斥信号量mutex控制两者不能同时操作缓冲区;由于存在同步关系,我设置了两个信号量emptyfull分别表示缓冲区中的资源数和缓冲区中的空位置数。mutex初值为1,empty初值为0,full初值为缓冲区大小。

最后,进行对生产者和消费者的行为设计:

针对生产者,生产者生产资源,先用P(full)判断缓冲区是否有空,再用P(mutex)判断是否有人在用缓冲区,若缓冲区有空且无人用,则生产者将资源放入缓冲区。放完后,先用V(mutex)释放缓冲区的使用权,再用V(empty)将缓冲区中的资源数加1,生产者进程结束。

针对消费者,消费者先用P(empty)判断缓冲区中是否有资源,再用P(mutex)判断缓冲区是否有人用,若缓冲区有资源且无人用,则消费者从缓冲区中取资源。取完后,先用V(mutex)释放缓冲区的使用权,再用V(full)将缓冲区中的空位置数加1,消费者进程结束。

1.3 代码及运行结果

生产者 / 消费者问题的C语言代码实现如下:

/*****************************************************************问题:多个生产者,多个消费者,有限缓冲区*描述:*1.两个进程对同一个内存资源进行操作,一个是生产者,一个是消费者。*2.生产者往共享内存资源填充数据,如果区域满,则等待消费者消费数据。*3.消费者从共享内存资源取数据,如果区域空,则等待生产者填充数据。*4.生产者的填充数据行为和消费者的消费数据行为不可在同一时间发生。
****************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>#define N 5  //生产者N个,消费者N个
#define BUFFERSIZE 3  //缓冲区大小sem_t mutex;  //互斥信号量
sem_t empty;  //缓冲区中的资源数
sem_t full;   //缓冲区的空位置数void *producer(void *arg) {int i = *((int *) arg);//生产者生产资源printf("The %dth producer is producing...\n", i);sleep(3);sem_wait(&full);  //判断缓冲区是否有空sem_wait(&mutex); //判断是否有人在用缓冲区//若缓冲区有空且无人用,生产者将资源放入缓冲区printf("The %dth producer is appending...\n", i);sleep(3);sem_post(&mutex); //生产者退出缓冲区sem_post(&empty); //缓冲区的资源数增加  
}void *consumer(void *arg) {int i = *((int *) arg);sem_wait(&empty); //判断缓冲区中是否有资源sem_wait(&mutex); //判断是否有人在用缓冲区//若缓冲区中有资源且无人用,消费者从缓冲区取资源printf("The %dth consumer is taking...\n", i);sleep(3);sem_post(&mutex); //消费者退出缓冲区sem_post(&full);  //缓冲区的空位置数增加//消费者消耗资源printf("The %dth consumer is consuming...\n", i);sleep(3);
}int main() {int i;pthread_t proThread[N];pthread_t conThread[N];int proId[N];int conId[N];sem_init(&mutex, 0, 1);  //初始化互斥信号量为1sem_init(&empty, 0, 0);  //初始化缓冲区中的资源数为0sem_init(&full, 0, BUFFERSIZE);  //初始化缓冲区中的空位置等于缓冲区大小for (i = 0; i < N; i++) {proId[i] = i;conId[i] = i;pthread_create (&proThread[i], NULL, producer, &proId[i]);//创建生产者线程pthread_create (&conThread[i], NULL, consumer, &conId[i]);//创建消费者线程}for ( i = 0; i < N; i++) {pthread_join(proThread[i], NULL);//等待所有的生产者线程执行完毕再结束pthread_join(conThread[i], NULL);//等待所有的消费者线程执行完毕再结束}return 0;
}

运行结果如下图所示:
生产者/消费者问题运行结果

2. 读者 / 写者问题

2.1 基本特点

读者/写者问题具体表现为:

  1. 一个进程在读的时候,其他进程也可以读。
  2. 一个进程在读/写的时候,其他进程不能进行写/读。
  3. 一个进程在写的时候,其他进程不能写。

2.2 解决思路

首先,分析其中存在的同步互斥关系:读者-写者之间没有明显的同步关系,它们不需要合作完成某件事情;读者-写者之间的互斥关系表现为两者不能同时访问文件。

然后,根据存在的互斥关系设置信号量:由于读者-写者的互斥,我设置了一个互斥信号量wsem来控制读者和写者的互斥访问。但如果只设置了这一个信号量,读者和读者之间的互斥也出现了。因为可能会有多个读者,所以我又设置了一个变量readcount记录读者的数量。这时,readcount又需要实现多个读者对它的互斥访问,为此,我设置了一个互斥信号量xwsemx的初值均为1,readcount的初值为0,现在所有的信号量已经设置好了。

最后,进行行为设计:读者 / 写者问题有读者优先与写者优先两种解决思路。

2.2.1 读者优先

读者优先的解决思路如下:

针对读进程,首先用P(x)判断是否有人在更新readcount,若无人在改动readcount,则将readcount加1。如果加1后的readcount等于1,则说明加1前的readcount为0,此时的进程为第一个读进程。第一个读进程出现,就要用P(wsem)来限制写进程的访问。然后,用V(x)释放readcount的更新权,读者开始读。读完后,再用P(x)重新获取readcount的更新权,将读进程的数量readcount减1。如果减1后的readcount等于0,则说明所有的读进程都读完了,可以用V(wsem)释放读/写的访问权了。最后,再用V(x)释放readcount的更新权。读进程结束。

针对写进程,首先用P(wsem)获取写的访问权,不让其他读/写进程访问。然后该写进程开始写,写完再用V(wsem)释放读/写的访问权。写进程结束。

2.2.2 写者优先

写者优先与读者优先的很大不同是,如果同时有读写进程在等待,要保证在等待的写进程比在等待的读进程优先执行。为此,设置了信号量z,保证等待的写进程可以跳过它前面等待的读进程。在读者优先的信号量设置基础上,增加了互斥信号量rsem控制写进程想写时,不允许新的读进程来读。增加了整型变量writecount记录等待的写者数,因writecount是共享变量,因此还要设置新的互斥信号量y以实现进程对writecount的互斥访问。

行为设计如下:

针对读进程,首先用P(z)保证写者优先,然后用P(rsem)判断有没有写进程在临界区,有,则等待;没有,则不让新的写进程进入临界区。接下来用P(x)开始对readcount的互斥访问,更新读进程的数量,第一个读进程用P(wsem)判断是否有写进程在进行写操作,有,则需要等待;没有,则不让写进程进行新写操作,用V(x)结束对readcount的互斥访问,用V(rsem)给写进程进入临界区的权利。然后V(z),可以开始读了。读完后的行为与读者优先时一样。读进程结束。

针对写进程,首先用P(y)开始对writecount的互斥访问,更新写进程的数量,第一个写进程需要判断是否有读进程在临界区,有的话需要等待,没有的话不让新的读进程进来。然后,用V(y)结束对writecount的互斥访问。接着就是写进程的互斥写操作了,同一时刻只有一个写进程可以写,这些行为也与读者优先时一样。在写完后,用P(y)开始对writecount的互斥访问,更新写进程数量。对最后一个离开临界区的写进程,用V(rsem)给读进程可以进临界区的权利,最后用V(y)结束对writecount的互斥访问。写进程结束。

2.3 代码及运行结果

2.3.1 读者优先

读者 / 写者问题(读者优先)的C语言代码实现如下:

/***************************************************************问题:读者/写者问题,读者优先*描述:*1.一个进程在读的时候,其他进程也可以读。*2.一个进程在读/写的时候,其他进程不能进行写/读。*3.一个进程在写的时候,其他进程不能写。*4.当至少有一个读进程在读时,后来的读进程无须等待,可直接加入。
**************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>#define N 10  //读者N个,写者N个int readcount;  //记录读进程的数量
sem_t x;     //x控制readcount的互斥访问
sem_t wsem;  //wsem对写互斥控制void *reader(void *arg) {int i = *((int *) arg);sem_wait(&x);readcount++;if (readcount == 1) { //第一个读进程出现,锁住不让写sem_wait(&wsem);}sem_post(&x);printf("The %dth reader is reading...\n", i);sleep(3);sem_wait(&x);readcount--;if (readcount == 0) { //所有的读进程读完,释放写的访问sem_post(&wsem);}sem_post(&x);
}void *writer(void *arg) {int i = *((int *) arg);sem_wait(&wsem); //锁住不让其他写进程写printf("The %dth writer is writing...\n", i);sleep(3);sem_post(&wsem); //释放写的访问
}int main() {int i;pthread_t rdThread[N];pthread_t wtThread[N];int rdId[N];int wtId[N];readcount = 0;//初始化信号量sem_init(&x, 0, 1);sem_init(&wsem, 0, 1);for (i = 0; i < N; i++) {rdId[i] = i;wtId[i] = i;pthread_create (&rdThread[i], NULL, reader, &rdId[i]);//创建读者线程pthread_create (&wtThread[i], NULL, writer, &wtId[i]);//创建写者线程}for ( i = 0; i < N; i++) {pthread_join(rdThread[i], NULL);//等待所有的读者线程执行完毕再结束pthread_join(wtThread[i], NULL);//等待所有的写者线程执行完毕再结束}return 0;
}

运行结果如下图所示:
读者优先的运行结果

2.3.2 写者优先

读者 / 写者问题(写者优先)的C语言代码实现如下:

/***************************************************************问题:读者/写者问题,写者优先*描述:*1.一个进程在读的时候,其他进程也可以读。*2.一个进程在读/写的时候,其他进程不能进行写/读。*3.一个进程在写的时候,其他进程不能写。*4.写进程声明想写时,不允许新的读进程来访问数据
**************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>#define N 10  //读者N个,写者N个int readcount;  //记录读进程的数量
int writecount; //记录写进程的数量
sem_t x;     //x控制readcount的互斥访问
sem_t y;     //y控制writecount的互斥访问
sem_t z;     //z保证写跳过读,保证写优先
sem_t wsem;  //wsem对写互斥控制
sem_t rsem;  //rsem对读互斥控制void *reader(void *arg) {int i = *((int *) arg);sem_wait(&z);sem_wait(&rsem);sem_wait(&x);readcount++;if (readcount == 1) { //第一个读进程出现,锁住不让写sem_wait(&wsem);}sem_post(&x);sem_post(&rsem);  //释放读的访问,允许其他读者进入sem_post(&z);printf("The %dth reader is reading...\n", i);sleep(3);sem_wait(&x);readcount--;if (readcount == 0) { //所有的读进程读完,释放写的访问sem_post(&wsem);}sem_post(&x);
}void *writer(void *arg) {int i = *((int *) arg);sem_wait(&y);writecount++;if(writecount == 1) { //第一个写进程,判断是否有读进程正在进行sem_wait(&rsem);}sem_post(&y);sem_wait(&wsem); //锁住不让其他写进程写printf("The %dth writer is writing...\n", i);sleep(3);sem_post(&wsem); //释放写的访问sem_wait(&y);writecount--;if (writecount == 0) { //所有写进程写完,释放读的访问sem_post(&rsem);}sem_post(&y);
}int main() {int i;pthread_t rdThread[N];pthread_t wtThread[N];int rdId[N];int wtId[N];readcount = 0;writecount = 0;//初始化信号量sem_init(&x, 0, 1);sem_init(&y, 0, 1);  sem_init(&z, 0, 1);  sem_init(&wsem, 0, 1);sem_init(&rsem, 0, 1);for (i = 0; i < N; i++) {rdId[i] = i;wtId[i] = i;pthread_create (&rdThread[i], NULL, reader, &rdId[i]);//创建读者线程pthread_create (&wtThread[i], NULL, writer, &wtId[i]);//创建写者线程}for ( i = 0; i < N; i++) {pthread_join(rdThread[i], NULL);//等待所有的读者线程执行完毕再结束pthread_join(wtThread[i], NULL);//等待所有的写者线程执行完毕再结束}return 0;
}

运行结果如下图所示:
写者优先的运行结果

3. 哲学家问题

3.1 基本特点

哲学家问题的具体表现为:
有N个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的N张椅子上,在圆桌上有N个碗和N支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,进餐完毕,放下筷子又继续思考。

约束条件如下:

  1. 只有拿到两只筷子时,哲学家才能吃饭。
  2. 如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
  3. 任一哲学家在自己未拿到两只筷子吃饭前,不会放下手中拿到的筷子。
  4. 用完之后将筷子返回原处。

3.2 解决思路

首先,分析其中存在的同步互斥关系:筷子是临界资源,每根筷子只能一个人取,这是互斥关系;如果筷子被取走,那么需要等待,这是同步关系。

可能出现死锁的错误解法是:设置一个信号量表示一只筷子,有N只筷子,所以设置N个信号量,哲学家每次饥饿时先试图拿左边的筷子,再试图拿右边的筷子,拿不到则等待,拿到了就吃饭,最后逐个放下筷子。这种解法下,如果N个哲学家同时感到饥饿,同时试图拿左边的筷子,都没成功;又同时试图拿右边的筷子,又都没成功,由于第3个约束条件的存在,这时出现了死锁。

因此,此问题的关键是互斥及避免死锁。在错误解法的基础上,一种可行解法是让奇数号与偶数号的哲学家拿筷子的顺序不同,破坏环路等待条件。

第二种可行的解法是只允许N-1位哲学家同时进餐,这样N-1个人都拿起一根筷子时,第N个人不能再拿筷子,就空出了一根筷子。

3.3 代码及运行结果

3.3.1 方法1

编号为奇数的哲学家先拿左手的筷子,编号为偶数的哲学家先拿右手的筷子,C语言代码实现如下:

/************************************************************************问题:哲学家问题*描述:*五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,*在桌子上有五个碗和五根筷子,他们的状态是思考和进餐交替,*平时,一个哲学家思考,饿了就取离他最近的筷子,只有拿到了两只筷子才能进餐。*进餐毕,放下筷子继续思考。*方法1:编号为奇数的哲学家先拿左手的筷子,编号为偶数的哲学家先拿右手的筷子
***********************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>#define N 5/*一共N根筷子,每根筷子设置一个信号量,记录筷子的状态*/
sem_t chopsticks[N]; //1代表筷子已经被用过,0代表筷子正等待被使用void *philosopher(void *arg) {int i = *((int *) arg);//为避免死锁,编号为奇数的哲学家先拿左手的筷子,编号为偶数的哲学家先拿右手的筷子if (i % 2) { //奇数编号sem_wait(&chopsticks[i]);  //先拿左手的筷子sleep(1);sem_wait(&chopsticks[(i + 1) % N]);  //再拿右手的筷子//哲学家吃啊吃printf("The %dth philosopher is eating...\n", i);sleep(3); sem_post(&chopsticks[(i + 1) % N]);sleep(1);sem_post(&chopsticks[i]);//哲学家想啊想printf("The %dth philosopher is thinking...\n", i);sleep(3);  }else {sem_wait(&chopsticks[(i + 1) % N]);  //先拿右手的筷子sleep(1);sem_wait(&chopsticks[i]);  //再拿左手的筷子//哲学家吃啊吃printf("The %dth philosopher is eating...\n", i);sleep(3); sem_post(&chopsticks[i]);sleep(1);sem_post(&chopsticks[(i + 1) % N]);//哲学家想啊想printf("The %dth philosopher is thinking...\n", i);sleep(3);  }
}int main() {int i;pthread_t thread[N];int id[N];  //记录哲学家编号for (i = 0; i < N; i++) { //初始化信号量为1sem_init(&chopsticks[i], 0, 1);}for (i = 0; i < N; i++) {id[i] = i;pthread_create (&thread[i], NULL, philosopher, &id[i]);//创建线程}for ( i = 0; i < N; i++) {pthread_join(thread[i], NULL);//等待所有的线程执行完毕再结束}return 0;
}

运行结果如下图所示:
哲学家问题方法1运行结果

3.3.2 方法2

只允许N-1位哲学家同时进入餐厅,C语言代码实现如下:

/************************************************************************问题:哲学家问题*描述:*五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,*在桌子上有五个碗和五根筷子,他们的状态是思考和进餐交替,*平时,一个哲学家思考,饿了就取离他最近的筷子,只有拿到了两只筷子才能进餐。*进餐毕,放下筷子继续思考。*方法2:只允许4位哲学家同时进入餐厅
***********************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>#define N 5/*一共N根筷子,每根筷子设置一个信号量,记录筷子的状态*/
sem_t chopsticks[N]; //1代表筷子已经被用过,0代表筷子正等待被使用
sem_t room;void *philosopher(void *arg) {int i = *((int *) arg);sem_wait(&room);sleep(1);sem_wait(&chopsticks[i]);  //先拿左手的筷子sleep(1);sem_wait(&chopsticks[(i + 1) % N]);  //再拿右手的筷子//哲学家吃啊吃printf("The %dth philosopher is eating...\n", i);sleep(3); sem_post(&chopsticks[(i + 1) % N]);sleep(1);sem_post(&chopsticks[i]);sleep(1);sem_post(&room);//哲学家想啊想printf("The %dth philosopher is thinking...\n", i);sleep(3);  
}int main() {int i;pthread_t thread[N];int id[N];  //记录哲学家编号for (i = 0; i < N; i++) { //初始化筷子信号量为1sem_init(&chopsticks[i], 0, 1);}sem_init(&room, 0, 4);   //初始化room信号量为4for (i = 0; i < N; i++) {id[i] = i;pthread_create (&thread[i], NULL, philosopher, &id[i]);//创建线程}for ( i = 0; i < N; i++) {pthread_join(thread[i], NULL);//等待所有的线程执行完毕再结束}return 0;
}

运行结果如下图所示:
哲学家问题方法2运行结果

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

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

相关文章

制造领域 物料清单(BOM)与零件明细表的区别

有许多人分不清物料清单(BOM)与零件明细表的区别,其实它们在企业的生产管理软件中起着不同的作用,各有各的特色,但是却有不尽相同。接下来我们就来区分一下吧 物料清单(BOM)&#xff0c;是详细记录一个项目所用到的所有下阶材料及相关属性&#xff0c;亦即母件与所有子件的从属…

求职应聘找工作,你一定会遇到的人才测评

信息时代&#xff0c;越来越多的公司在招聘时引入了人才测评机制。企业和单位希望通过人才测评在广大的应聘者中&#xff0c;找到符合自己要求的人才。虽然很多应聘者能力和简历都比较出众&#xff0c;但却在最开始的人才测评中吃了亏。有的公司很看重人才测评结果。测评就相当…

76.Go分布式ID总览

文章目录 简介一&#xff1a;UUID二、雪花算法三&#xff1a;Leaf-snowflake四&#xff1a;数据库自增ID五&#xff1a;使用Redis实现分布式ID生成六&#xff1a;使用数据库分段&#xff08;Leaf-segment&#xff09;七 &#xff1a;增强版Leaf-segment八&#xff1a;Tinyid九&…

Vue实现图片预览,侧边栏懒加载,不用任何插件,简单好用

实现样式 需求 实现PDF上传预览&#xff0c;并且不能下载 第一次实现&#xff1a;用vue-pdf&#xff0c;将上传的文件用base64传给前端展示 问题&#xff1a; 水印第一次加载有后面又没有了。当上传大的pdf文件后&#xff0c;前端获取和渲染又长又慢&#xff0c;甚至不能用 修…

Docker K8s-存储相关概念

Docker中的存储有两个概念&#xff1a;存储驱动程序Storage Driver和卷驱动程序Volumes Drivers。 存储驱动 Storage Driver 首先我们来看一下安装docker以后&#xff0c;docker的文件夹下面有哪些内容&#xff1a; cd /var/lib/docker && ll这里存储了所有的数据&a…

力扣hot100 轮转数组 一题多解 翻转数组

Problem: 189. 轮转数组 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public void rotate(int[] nums, int k){int n nums.length;k k % n;reverse(…

Git搭建

文件格式 <VirtuaHost * 80> nginx </virtualHost> pache xml server {} nginx conf格式 [xx] 配置内容 代码开发中版本控制,项目代码编译构建,项目版本迭代全流程 命令300条 Hospital Information System 开发语言分类: 编译型: C nginx ma…

ArcEngine添加点要素、线要素、面要素及学习总结

基于C#的ArcEngine二次开发教程&#xff08;13&#xff09;&#xff1a;点、线、面要素的绘制_arcengine onmousedown-CSDN博客 https://www.cnblogs.com/cannel/p/11074343.html ArcEngine绘制点、线、多边形、矩形、圆形、椭圆的代码_arcengine 开发 生成矩形-CSDN博客 https…

DocsOpenApi自动化校验

一、背景 生产环境的文档中心的OpenApi和Kong服务配置的OpenApi可能存在不一致的情况&#xff0c;比如生产环境的文档中心有某个OpenApi&#xff0c;但是Kong服务没有配置到&#xff0c;那客户使用就会有问题。所以&#xff0c;前段时间&#xff08;M09版本&#xff09;花了4个…

MS7256C:L1 频段卫星导航射频前端低噪声放大器芯片

1、描述 MS7256C 是一款具有高增益、低噪声系数的低噪声放 大器&#xff08;LNA&#xff09;芯片&#xff0c;支持 L1 频段多模式全球卫星定位&#xff0c;可 以应用于 GPS、北斗二代、伽利略、Glonass 等 GNSS 导航 接收机中。芯片采用先进工艺制造&#xff0c;封装采用 1…

HDD的烦恼:HAMR会让SMR黯然失色吗?

HDD相关阅读参考&#xff1a; HDD回暖于2024&#xff0c;与SSD决战于2028 HDD最后的冲刺&#xff1a;大容量硬盘的奋力一搏 叠瓦式磁记录技术&#xff08;SMR&#xff09;自20世纪90年代起开始研究&#xff0c;于2010年后逐渐商业化应用于高密度硬盘。该技术的核心理念在于通…

从一个简单的Ping案例来分析二层,三层的数据包封装过程

1. 应用程序生成数据DATA&#xff0c;加上传输层报文头(TCP/UDP Head),调用网络层服务&#xff08;IP包头中的源地址由主机网卡直接得到&#xff0c;目的IP则由我们在使用应用程序时输入得到&#xff0c;如果是基于域名&#xff0c;调用一个通信过程DNS来获得目的IP&#xff0…

便捷接口调测:API 开发工具大比拼 | 开源专题 No.62

hoppscotch/hoppscotch Stars: 56.1k License: MIT Hoppscotch 是一个开源的 API 开发生态系统&#xff0c;主要功能包括发送请求和获取实时响应。该项目具有以下核心优势&#xff1a; 轻量级&#xff1a;采用简约的 UI 设计。快速&#xff1a;实时发送请求并获得响应。支持多…

独立站怎么建设对seo好?

现如今市面上就有不少开源的建站程序可供挑选&#xff0c;哪怕你不懂技术&#xff0c;不懂代码&#xff0c;也能建自己的独立站&#xff0c;效果比不少所谓的用自己技术开发的站都要好&#xff0c;本身做一个网站不难&#xff0c;但你做网站的目的是什么&#xff1f;是为了在搜…

【开源】基于JAVA语言的人事管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员功能模块2.2 普通员工功能模块2.3 答辩文案 三、系统展示四、核心代码4.1 查询职称4.2 新增留言回复4.3 工资申请4.4 工资审核4.5 员工请假 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的人…

Spring 声明式事务 @Transactional(详解)【面试重点,小林出品】

关于 Transactional 注解的基本使用&#xff0c;推荐看Spring 声明式事务 Transactional&#xff08;基本使用&#xff09; 概述 本篇博客主要学习 Transactional 注解当中的三个常⻅属性: 1. rollbackFor:异常回滚属性.指定能够触发事务回滚的异常类型.可以指定多个异常类型 …

AIGC是什么?GPT-4.0、DALL·E以及Midjourney等多种智能服务

AIGC&#xff08;人工智能生成内容&#xff0c;Artificial Intelligence Generated Content&#xff09;是指利用人工智能技术自动生成的文本、图像、音频和视频等内容。随着技术的进步&#xff0c;AIGC已经成为创意产业和内容创作领域的一股新兴力量。MidTool作为一款集成了多…

qml与C++的交互

qml端使用C对象类型、qml端调用C函数/c端调用qml端函数、qml端发信号-连接C端槽函数、C端发信号-连接qml端函数等。 代码资源下载&#xff1a; https://download.csdn.net/download/TianYanRen111/88779433 若无法下载&#xff0c;直接拷贝以下代码测试即可。 main.cpp #incl…

mmpose 2d姿态预测值转json文件

目录 效果图: 参考 模板文件下载地址: python预测代码: 效果图: <

51单片机ESP8266

一、MQTT透传AT固件 安信可提供的烧录WiFi固件工具&#xff1a; 链接: https://docs.ai-thinker.com/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B72 安信可提供的固件库链接: https://docs.ai-thinker.com/%E5%9B%BA%E4%BB%B6%E6%B1%87%E6%80%BB 经过测试&#xff0c;选择这个不可以…