互斥锁与条件变量学习与应用小结

打印 上一主题 下一主题

主题 865|帖子 865|积分 2597

互斥锁,也叫互斥量。有以下几个显著的特点:


  • 唯一性:互斥锁保证在任何给定的时间点,只有一个线程可以获得对临界区资源的访问权。假如一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
  • 原子性:锁定息争锁互斥锁的操作是原子的,这意味着操作系统(或pthread函数库)保证了假如一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。
  • 非繁忙等候:当一个线程已经锁定了一个互斥量,其他试图锁定这个互斥量的线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。被挂起的线程在锁被释放后会被唤醒并继续实行。
  • 保护共享资源:互斥锁用于保护临界区资源免受多个线程同时访问和修改的影响,确保数据的完整性和一致性。
    ​        另外,互斥锁,也成为“协同锁”或“建议锁”。当 A线程对某个全局变量加锁访问,8在访问前尝试加锁,拿不到锁,8阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。
    ​        固然它提供了锁定机制来制止多线程同时访问共享资源造成的竞态条件,但并没有强制限定线程必须遵循这一机制。也就是说,即使有互斥锁存在,假如线程不按照规则来访问数据,依然大概造成数据混乱。也就是说,互斥锁的有效性依赖于编程者的合作。因此,编程时需要根据编程人员的规则使用
条件变量不是锁,必须与互斥锁一起配合使用,因此在这同时记载两者的API接口及用法。
互斥锁使用时,有几个技巧小结如下:


  • 尽量保证锁的粒度,越小越好。(即访问共享数据前,加锁,访问结束要立即解锁,让其他线程能访问到的概率更大,保证较高的并发度
  • 将互斥锁变量mutex当作整数1,每次上锁即申请资源,mutex--;解锁即释放资源,mutex++,类似于信号量。
常用函数枚举如下:
  1. pthread_mutex_t mutex (= PTHREAD_MUTEX_INITIALIZER);                                // 互斥锁变量初始定义,本质是一个结构体,应用时可忽略
  2. int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *mutexattr); // 初始化(若已经用宏定义进行初始化,则不需要调用此函数),第二个参数一般用NULL。
  3. //此处的restrict是关键字,表示对于 mutex指针指向的空间操作,均只能由mutex指针完成,不能依靠传递地址靠其他变量完成,换句话说,就是告诉编译器不会有其他指针指向同一块内存,从而允许编译器进行更高效的优化。
  4. int pthread_mutex_lock(pthread_mutex_t *mutex);                                       // 上锁
  5. int pthread_mutex_trylock(pthread_mutex_t *mutex);                                       // 尝试进行上锁
  6. int pthread_mutex_unlock(pthread_mutex_t *mutex);                                    // 解锁
  7. int pthread_mutex_destroy(pthread_cond_t *cond);                                      // 销毁
复制代码
条件变量的特点罗列如下:


  • 等候与关照机制:条件变量允许线程在某个特定条件不满意时进入等候状态(等候在条件变量上)。当其他线程改变了条件,并以为等候的线程应该被唤醒时,它会使用条件变量的关照(signal)或广播(broadcast)功能来唤醒等候的线程。
  • 与互斥锁结合使用:条件变量必须与互斥锁一起使用,以确保在检查和修改条件时的原子性。在调用条件变量的等候函数时,锁定互斥锁,然后检查条件。假如条件不满意,则调用条件变量的等候函数并释放互斥锁,进入等候状态。当条件变量被关照后,线程会重新获取互斥锁并继续实行。
  • 制止忙等候:使用条件变量可以制止线程在条件不满意时一连检查条件(即忙等候),这样可以节流CPU资源。线程在等候条件变量时会被挂起,直到被其他线程关照。
  • 广播与关照:条件变量通常提供关照(notify)和广播(notifyAll)功能。关照只会唤醒等候在条件变量上的一个线程,而广播会唤醒所有等候在条件变量上的线程。
常用函数枚举如下:
  1. pthread_cond_t cond (= PTHREAD_COND_INITIALIZER);                                                       // 条件变量初始定义
  2. int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);                               // 初始化(若已经用宏定义进行初始化,则不需要调用此函数)
  3. int pthread_cond_signal(pthread_cond_t *cond);                                                            // 唤醒一个等待中的线程
  4. int pthread_cond_broadcast(pthread_cond_t *cond);                                                         // 唤醒全部线程
  5. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);                                      // 等待被唤醒
  6. int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); // 等待一段时间(abstime),
  7. int pthread_cond_destroy(pthread_cond_t *cond);                                                           // 销毁
复制代码
应用举例如下:
  1. /**
  2. * @file name:        互斥锁与条件变量的应用展示
  3. * @brief         :  子线程功能详note
  4. * @author ni456xinmie@163.com
  5. * @date 2024/06/01
  6. * @version 1.0 :版本
  7. * @property :
  8. * @note
  9. *          子线程B:当全局变量x和y满足条件时(>100),进行打印输出
  10. *          子线程C:对共享资源x和y进行每次+50的操作
  11. *          子线程D:对共享资源x和y进行每次+30的操作,并调整sleep函数与unlock函数的位置,与子线程C进行对比
  12. * CopyRight (c)  2023-2024   ni456xinmie@163.com   All Right Reseverd
  13. */
  14. #include <pthread.h> //关于线程API接口的头文件   编译时需要指定  -pthread
  15. #include <stdio.h>
  16. #include <unistd.h>
  17. int x = 0, y = 0; // 共享资源
  18. pthread_cond_t cond_flag = PTHREAD_COND_INITIALIZER; // 此处已用宏定义,就不用初始化函数进行初始化了
  19. pthread_mutex_t mutex_flag = PTHREAD_MUTEX_INITIALIZER;
  20. void *task_B(void *arg)
  21. {
  22.     while (1)
  23.     {
  24.         pthread_mutex_lock(&mutex_flag); // 上锁,“申请资源”
  25.         while (x < 100 && y < 100)
  26.         {
  27.             pthread_cond_wait(&cond_flag, &mutex_flag); // 条件阻塞,即当x,y<100时,进行挂起等待其他线程的信号
  28.         }
  29.         printf("x 和y已达到要求:%d %d\n", x, y);
  30.         x = 0;
  31.         y = 0;
  32.         pthread_mutex_unlock(&mutex_flag); // 解锁,“释放资源”
  33.         sleep(2);                          // 为保证输出效果进行延时
  34.     }
  35. }
  36. void *task_C(void *arg)
  37. {
  38.     while (1)
  39.     {
  40.         pthread_mutex_lock(&mutex_flag); // 上锁,“申请资源”
  41.         x += 50;
  42.         y += 50;                                                 //申请到资源后对x和y进行操作
  43.         printf("子线程C对x和y进行操作:%d %d\n", x, y);
  44.         if (x > 100 && y > 100)
  45.         {
  46.             pthread_cond_signal(&cond_flag);
  47.         }                                                                   //满足条件后,发送一条信号给子线程B
  48.         pthread_mutex_unlock(&mutex_flag); // 解锁,“释放资源”
  49.         sleep(2);                          // 为方便展示,进行延迟
  50.     }
  51. }
  52. void *task_D(void *arg)
  53. {
  54.     while (1)
  55.     {
  56.         pthread_mutex_lock(&mutex_flag); // 同子线程C
  57.         x += 30;
  58.         y += 30;
  59.         printf("子线程C对x和y进行操作:%d %d\n", x, y);
  60.         if (x > 100 && y > 100)
  61.         {
  62.             pthread_cond_signal(&cond_flag);
  63.         }
  64.         sleep(2);                          // 此处与子线程C进行对比,先sleep,再解锁
  65.         pthread_mutex_unlock(&mutex_flag);
  66.     }
  67. }
  68. int main(int argc, char const *argv[])
  69. {
  70.     // 1.创建子线程
  71.     pthread_t B_tid;
  72.     pthread_create(&B_tid, NULL, task_B, NULL); // 子线程B
  73.     pthread_t C_tid;
  74.     pthread_create(&C_tid, NULL, task_C, NULL); // 子线程C
  75.     pthread_t D_tid;
  76.     pthread_create(&D_tid, NULL, task_D, NULL); // 子线程C
  77.     // 2.主线程结束
  78.     pthread_exit(NULL);
  79.     return 0; // 主线程在上一条语句已经结束,这条语句永远不会执行
  80. }
复制代码
上述步伐仅运行子线程B与C时,输出结果同预期,如下:
  1. 子线程C对x和y进行操作:50 50
  2. 子线程C对x和y进行操作:100 100
  3. 子线程C对x和y进行操作:150 150
  4. x 和y已达到要求:150 150
  5. 子线程C对x和y进行操作:50 50
  6. 子线程C对x和y进行操作:100 100
  7. 子线程C对x和y进行操作:150 150
  8. x 和y已达到要求:150 150
  9. 子线程C对x和y进行操作:50 50
  10. 子线程C对x和y进行操作:100 100
  11. 子线程C对x和y进行操作:150 150
  12. x 和y已达到要求:150 150
复制代码
上述步伐运行子线程B,C与D时,输出结果如下:
  1. 子线程C对x和y进行操作:50 50
  2. 子线程C对x和y进行操作:80 80
  3. 子线程C对x和y进行操作:110 110
  4. 子线程C对x和y进行操作:140 140
  5. 子线程C对x和y进行操作:170 170
  6. 子线程C对x和y进行操作:200 200
  7. 子线程C对x和y进行操作:230 230
  8. 子线程C对x和y进行操作:260 260
  9. 子线程C对x和y进行操作:290 290
  10. 子线程C对x和y进行操作:320 320
  11. 子线程C对x和y进行操作:350 350
  12. x 和y已达到要求:350 350
  13. 子线程C对x和y进行操作:30 30
  14. 子线程C对x和y进行操作:60 60
  15. 子线程C对x和y进行操作:90 90
  16. 子线程C对x和y进行操作:120 120
  17. 子线程C对x和y进行操作:150 150
复制代码
​        可见,子线程D抢占资源频繁,子线程C一直在等候资源,而子线程B满意条件收到信号以后,也无法抢到共享资源进行输出,并发度不高。因此,为了进步粒度,需要子线程D在对x和y进行操作后立即进行解锁,然后sleep阻塞,让其他线程有时机得到共享资源,进步并发度。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表