【Linux C

您所在的位置:网站首页 多线程的同步和互斥怎么求的 【Linux C

【Linux C

2024-06-02 21:49| 来源: 网络整理| 查看: 265

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭 ⏰发布时间⏰:

本文未经允许,不得转发!!!

目录 🎄一、概述🎄二、互斥量与信号量✨2.1 互斥量与信号量的相似之处✨2.2 互斥量与信号量的不同之处 🎄三、信号量与条件变量🎄四、互斥量与条件变量🎄五、总结

在这里插入图片描述

🎄一、概述

在Linux系统多线程编程过程中,常常需要处理多个线程同步的问题。在处理多线程同步问题时,最常见的三种方式是:互斥量、信号量、条件变量,关于这三种方式的使用,在前面使用了四篇文章进行介绍和总结了,文章链接如下,感兴趣的自行取用:

【Linux C | 多线程编程】线程同步 | 互斥量(互斥锁)介绍和使用 【Linux C | 多线程编程】线程同步 | 条件变量(万字详解) 【Linux C | 多线程编程】线程同步 | 条件变量 的 使用总结 【Linux C | 多线程编程】线程同步 | 信号量(无名信号量) 及其使用例子

本文主要是对互斥量、信号量、条件变量三种使用方式的对比和总结,可以帮助我们对这三种线程同步方式的用法加深理解。

下文会经常提到临界区,所谓临界区,是指同一时间只能容许一个进程进入的一系列操作。

在这里插入图片描述

🎄二、互斥量与信号量

关于互斥量与信号量,比较形象的比喻是:互斥量是一间厕所,每次只允许一人访问; 信号量类似一个公共卫生间,里面的4间(多个)厕所,可以同时允许4个人访问。

互斥量的本质是一把锁,其作用是对临界区进行安全地访问(独占访问),当多个线程访问临界区时,保证同一时刻只有一个线程访问临界区。互斥量有以下几个特点: 1、互斥量只有两种状态,已上锁、已解锁; 2、互斥量总是由给它上锁的线程解锁; 3、互斥量的访问方式是独占的,只有互斥量上锁了,其他线程就只能等待其访问完解锁,以此来保证一个时刻只有一个线程占有互斥量。

信号量是与某些资源相关联的,信号量初始化的信号量值表示资源个数。信号量值初始化为1则表示是二值信号量,若初始化的信号量值大于 1 则是计数信号量。信号量的特点如下: 1、二值信号量的取值只有两个:0、1;计数信号量的取值大于2个; 2、信号量可以在一个线程等待(减一),另一个线程发布(加一);也可以是同一个线程等待(减一)并发布(加一)。

✨2.1 互斥量与信号量的相似之处

互斥量和信号量有相同的地方,特别是二值信号量与互斥量甚至可以替换着使用,在Linux早期,就经历过将信号量替换成互斥量的过程,下图来源于网络,说明了这个替换过程: 在这里插入图片描述 比起互斥量,信号量先被设计和实现出来,且二值信号量可以起到互斥作用,所以早期的Linux代码使用了信号量来访问临界区。而互斥量最早是在2.6.16内核中由Red Hat Enterprise Linux的资深内核专家Ingo Molnar设计和实现的,之前的内核代码使用信号量也没有问题,但是互斥量在锁争用激烈的情况下,互斥量比信号量执行速度更快,可扩展性更好。

下面是使用互斥量访问临界区的示例代码:

// 08_mutex_test.c // gcc 08_mutex_test.c -lpthread #include #include #include int g_Count = 0; pthread_mutex_t g_mutex; void *func(void *arg) { int i=0; for(i=0; i pthread_mutex_init(&g_mutex, NULL); // 创建4个线程 pthread_t threadId[4]; int i=0; for(i=0; i pthread_join(threadId[i],NULL); printf("join threadId=%lx\n",threadId[i]); } printf("g_Count=%d\n",g_Count); pthread_mutex_destroy(&g_mutex); return 0; }

下面是使用信号量访问临界区的示例代码,可以与上一份代码对比,就可以知道互斥量和信号量的用于互斥时是相同的:

// 10_sem_mutex.c // gcc 10_sem_mutex.c -l pthread #include #include #include int g_Count = 0; sem_t g_sem; void *func(void *arg) { int i=0; for(i=0; i sem_init(&g_sem, 0, 1); // 创建4个线程 pthread_t threadId[4]; int i=0; for(i=0; i pthread_join(threadId[i],NULL); printf("join threadId=%lx\n",threadId[i]); } printf("g_Count=%d\n",g_Count); sem_destroy(&g_sem); return 0; } ✨2.2 互斥量与信号量的不同之处

互斥量与信号量的不同之处也很多,但主要的有两点: 1、互斥量总是在同一个线程加锁、解锁;信号量可以在一个线程等待(减一),另一线程发布(加一)。 2、互斥量只有两个状态:已加锁、已解锁;信号量可以初始为大于1的值,与多个资源关联。

关于信号量可以在不同线程等待、发布的例子,决定在其与条件变量对比时在介绍。下面是信号量关联多个资源的例子:

// 10_sem_multiple.c // gcc 10_sem_multiple.c -lpthread #include #include #include #include #include #define TOILET_NUM 4 #define PEOPLE_NUM 8 int toilets[TOILET_NUM] = {0,}; // 4个蹲厕 pthread_mutex_t toilet_mutex = PTHREAD_MUTEX_INITIALIZER; // toilets 的互斥量 sem_t g_sem; int getToilet() { int i=0; for(i=0; i int semvalue = 0; sem_getvalue(&g_sem, &semvalue); return semvalue; } // 上厕所线程 void *going_to_the_toilet(void *arg) { int id = *((int*)arg); int count = 2; while(count-->0){ printf("线程[%d] 等待厕所,厕所数量=%d\n",id, sem_value()); sem_wait(&g_sem); pthread_mutex_lock(&toilet_mutex); // 厕所有多个线程访问,加锁 int i = getToilet(); if(getToilet()==TOILET_NUM){ printf("线程[%d], No toilet\n",id); } else{ toilets[i] = 1; // 表示进入该厕所 printf("线程[%d] 进入厕所[%d], 即将工作 2s\n",id, i); pthread_mutex_unlock(&toilet_mutex); // 上厕所前先释放锁,让其他人可以访问厕所资源 sleep(2); // 正在上厕所... pthread_mutex_lock(&toilet_mutex); toilets[i] = 0; printf("线程[%d] 完成工作,厕所[%d]空闲\n",id, i); } pthread_mutex_unlock(&toilet_mutex); sem_post(&g_sem); sleep(1); // 释放资源后,休眠1秒,确保资源让出去 } return NULL; } int main() { sem_init(&g_sem, 0, TOILET_NUM);// 初始化信号量值为4 // 创建线程 pthread_t people_thid[PEOPLE_NUM]; int i=0, num[PEOPLE_NUM]={0,}; for(i=0; i pthread_join(people_thid[i], NULL); } sem_destroy(&g_sem); return 0; }

运行结果这里就不展示了,可以看看上篇文章,关于信号量的。

在这里插入图片描述

🎄三、信号量与条件变量

条件变量用来阻塞一个线程,直到某条件满足为止。 信号量也可以使一个线程陷入阻塞(等待信号量),直到另一个线程发布信号量。 仔细看上面两句话,分别描述了条件变量、信号量的功能,而且这两个功能看起来很类似,下面通过使用条件变量、信号量解决“生产者-消费者”的同步问题来了解条件变量、信号量使用上的区别。

下面是使用条件变量的代码:

// 09_producer_consumer_cond.c // gcc 09_producer_consumer_cond.c -lpthread #include #include #include #include #include #include "linux_list.h" #define COMSUMER_NUM 2 typedef struct _product { struct list_head list_node; int product_id; }product_t; struct list_head productList;// 头结点 pthread_mutex_t product_mutex = PTHREAD_MUTEX_INITIALIZER; // productList 的互斥量 pthread_cond_t product_cond = PTHREAD_COND_INITIALIZER; // 条件变量 // 生产者线程,1秒生成一个产品放到链表 void *th_producer(void *arg) { int id = 0; while(1) { product_t *pProduct = (product_t*)malloc(sizeof(product_t)); pProduct->product_id = id++; pthread_mutex_lock(&product_mutex); list_add_tail(&pProduct->list_node, &productList); pthread_cond_signal(&product_cond); pthread_mutex_unlock(&product_mutex); sleep(1); } return NULL; } // 消费者线程,1秒消耗掉一个产品 void *th_consumer(void *arg) { while(1) { pthread_mutex_lock(&product_mutex); while(list_empty(&productList)) // 条件不满足 { pthread_cond_wait(&product_cond, &product_mutex); } // 不为空,则取出一个 product_t* pProduct = list_entry(productList.next, product_t, list_node);// 获取第一个节点 printf("consumer[%d] get product id=%d\n", *((int*)arg), pProduct->product_id); list_del(productList.next); // 删除第一个节点 free(pProduct); pthread_mutex_unlock(&product_mutex); } return NULL; } int main() { INIT_LIST_HEAD(&productList); // 初始化链表 // 创建生产者线程 pthread_t producer_thid; pthread_create(&producer_thid, NULL, th_producer, NULL); // 创建消费者线程 pthread_t consumer_thid[COMSUMER_NUM]; int i=0, num[COMSUMER_NUM]={0,}; for(i=0; i pthread_join(consumer_thid[i], NULL); } return 0; }

下面是使用信号量的代码:

// 10_producer_consumer_sem.c // gcc 10_producer_consumer_sem.c -lpthread #include #include #include #include #include #include #include "linux_list.h" #define COMSUMER_NUM 2 typedef struct _product { struct list_head list_node; int product_id; }product_t; struct list_head productList;// 头结点 pthread_mutex_t product_mutex = PTHREAD_MUTEX_INITIALIZER; // productList 的互斥量 sem_t g_sem; // 生产者线程,1秒生成一个产品放到链表 void *th_producer(void *arg) { int id = 0; while(1) { product_t *pProduct = (product_t*)malloc(sizeof(product_t)); pProduct->product_id = id++; pthread_mutex_lock(&product_mutex); list_add_tail(&pProduct->list_node, &productList); pthread_mutex_unlock(&product_mutex); sem_post(&g_sem); sleep(1); } return NULL; } // 消费者线程,1秒消耗掉一个产品 void *th_consumer(void *arg) { while(1) { pthread_mutex_lock(&product_mutex); while(list_empty(&productList)) // 条件不满足 { pthread_mutex_unlock(&product_mutex); sem_wait(&g_sem); pthread_mutex_lock(&product_mutex); } // 不为空,则取出一个 product_t* pProduct = list_entry(productList.next, product_t, list_node);// 获取第一个节点 printf("consumer[%d] get product id=%d\n", *((int*)arg), pProduct->product_id); list_del(productList.next); // 删除第一个节点 free(pProduct); pthread_mutex_unlock(&product_mutex); } return NULL; } int main() { INIT_LIST_HEAD(&productList); // 初始化链表 sem_init(&g_sem, 0, 1); // 初始化信号量 // 创建生产者线程 pthread_t producer_thid; pthread_create(&producer_thid, NULL, th_producer, NULL); // 创建消费者线程 pthread_t consumer_thid[COMSUMER_NUM]; int i=0, num[COMSUMER_NUM]={0,}; for(i=0; i pthread_join(consumer_thid[i], NULL); } sem_destroy(&g_sem); return 0; }

运行结果是两份代码都可以使用“生产者-消费者”模式之间的线程同步,但有以下区别: 1、条件变量的等待函数要求传入互斥量作为参数,所以条件变量必须和互斥量同时使用;信号量没有类似的要求,虽然访问临界区时会用到互斥量,但这不是信号量要求的。 2、条件变量要求“①查询条件、②阻塞等待”是一个原子操作;信号量则允许“①查询条件、②阻塞等待”不是一个原子操作。

条件变量之所以要求“①查询条件、②阻塞等待”是一个原子操作,是因为判断条件和pthread_cond_wait之间被打断的话,可能在判断条件之后,调用pthread_cond_wait之前,条件变量就被唤醒了,导致该线程一直等待下去。

信号量之所以不需要要求“①查询条件、②阻塞等待”是一个原子操作,是因为它是计数的,如果判断完条件之后,调用sem_wait(&g_sem);之前,信号量发布(加一)了,那么调用sem_wait(&g_sem);时直接不需要等待,直接继续执行,不存在唤醒丢失的情况。总之,条件变量唤醒时如果没有线程等待在该条件变量上,那么信号将丢失;而信号量有计数值,每次信号量post操作都会被记录。

在这里插入图片描述

🎄四、互斥量与条件变量

互斥量与条件变量不像前面那样有相似的功能,更多的是互补的关系。

互斥量主要功能是使各个线程独占地访问临界区。 条件变量的功能是提供线程阻塞等待、其他线程唤醒的同步操作。但是没有提供安全访问临界区的功能,所以使用条件变量都需要互斥量一同使用。

在这里插入图片描述

🎄五、总结

👉本文总结了“互斥量与信号量使用的区别”、“信号量与条件变量使用的区别”、互斥量与条件变量的关系,并给出部分例子解析这些差异的存在。

在这里插入图片描述 如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考: https://blog.csdn.net/tugouxp/article/details/68951576



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3