首页 > 其他分享 >互斥锁与条件变量学习与应用小结

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

时间:2024-06-01 23:54:26浏览次数:13  
标签:变量 cond 互斥 线程 pthread mutex 操作 小结

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

  1. 唯一性:互斥锁保证在任何给定的时间点,只有一个线程可以获得对临界区资源的访问权。如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。

  2. 原子性:锁定和解锁互斥锁的操作是原子的,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。

  3. 非繁忙等待:当一个线程已经锁定了一个互斥量,其他试图锁定这个互斥量的线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。被挂起的线程在锁被释放后会被唤醒并继续执行。

  4. 保护共享资源:互斥锁用于保护临界区资源免受多个线程同时访问和修改的影响,确保数据的完整性和一致性。

    ​ 另外,互斥锁,也成为“协同锁”或“建议锁”。当 A线程对某个全局变量加锁访问,8在访问前尝试加锁,拿不到锁,8阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。

    ​ 虽然它提供了锁定机制来避免多线程同时访问共享资源造成的竞态条件,但并没有强制限定线程必须遵循这一机制。也就是说,即使有互斥锁存在,如果线程不按照规则来访问数据,依然可能造成数据混乱。也就是说,互斥锁的有效性依赖于编程者的合作。因此,编程时需要根据编程人员的规则使用

条件变量不是锁,必须与互斥锁一起配合使用,因此在这同时记录两者的API接口及用法。

互斥锁使用时,有几个技巧小结如下:

  1. 尽量保证锁的粒度,越小越好。(即访问共享数据前,加锁,访问结束要立即解锁,让其他线程能访问到的概率更大,保证较高的并发度
  2. 将互斥锁变量mutex看成整数1,每次上锁即申请资源,mutex--;解锁即释放资源,mutex++,类似于信号量。

常用函数列举如下:

pthread_mutex_t mutex (= PTHREAD_MUTEX_INITIALIZER);                                // 互斥锁变量初始定义,本质是一个结构体,应用时可忽略
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *mutexattr); // 初始化(若已经用宏定义进行初始化,则不需要调用此函数),第二个参数一般用NULL。
//此处的restrict是关键字,表示对于 mutex指针指向的空间操作,均只能由mutex指针完成,不能依靠传递地址靠其他变量完成,换句话说,就是告诉编译器不会有其他指针指向同一块内存,从而允许编译器进行更高效的优化。
int pthread_mutex_lock(pthread_mutex_t *mutex);                                       // 上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);                                       // 尝试进行上锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);                                    // 解锁
int pthread_mutex_destroy(pthread_cond_t *cond);                                      // 销毁

条件变量的特点罗列如下:

  1. 等待与通知机制:条件变量允许线程在某个特定条件不满足时进入等待状态(等待在条件变量上)。当其他线程改变了条件,并认为等待的线程应该被唤醒时,它会使用条件变量的通知(signal)或广播(broadcast)功能来唤醒等待的线程。
  2. 与互斥锁结合使用:条件变量必须与互斥锁一起使用,以确保在检查和修改条件时的原子性。在调用条件变量的等待函数时,锁定互斥锁,然后检查条件。如果条件不满足,则调用条件变量的等待函数并释放互斥锁,进入等待状态。当条件变量被通知后,线程会重新获取互斥锁并继续执行。
  3. 避免忙等待:使用条件变量可以避免线程在条件不满足时持续检查条件(即忙等待),这样可以节省CPU资源。线程在等待条件变量时会被挂起,直到被其他线程通知。
  4. 广播与通知:条件变量通常提供通知(notify)和广播(notifyAll)功能。通知只会唤醒等待在条件变量上的一个线程,而广播会唤醒所有等待在条件变量上的线程。

常用函数列举如下:

pthread_cond_t cond (= PTHREAD_COND_INITIALIZER);                                                       // 条件变量初始定义
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);                               // 初始化(若已经用宏定义进行初始化,则不需要调用此函数)
int pthread_cond_signal(pthread_cond_t *cond);                                                            // 唤醒一个等待中的线程
int pthread_cond_broadcast(pthread_cond_t *cond);                                                         // 唤醒全部线程
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);                                      // 等待被唤醒
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); // 等待一段时间(abstime),
int pthread_cond_destroy(pthread_cond_t *cond);                                                           // 销毁

应用举例如下:

/**
 * @file name:	互斥锁与条件变量的应用展示
 * @brief	 :  子线程功能详note
 * @author [email protected]
 * @date 2024/06/01
 * @version 1.0 :版本
 * @property :
 * @note
 *          子线程B:当全局变量x和y满足条件时(>100),进行打印输出
 *          子线程C:对共享资源x和y进行每次+50的操作
 *          子线程D:对共享资源x和y进行每次+30的操作,并调整sleep函数与unlock函数的位置,与子线程C进行对比
 * CopyRight (c)  2023-2024   [email protected]   All Right Reseverd
 */

#include <pthread.h> //关于线程API接口的头文件   编译时需要指定  -pthread
#include <stdio.h>
#include <unistd.h>
int x = 0, y = 0; // 共享资源

pthread_cond_t cond_flag = PTHREAD_COND_INITIALIZER; // 此处已用宏定义,就不用初始化函数进行初始化了
pthread_mutex_t mutex_flag = PTHREAD_MUTEX_INITIALIZER;

void *task_B(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex_flag); // 上锁,“申请资源”
        while (x < 100 && y < 100)
        {
            pthread_cond_wait(&cond_flag, &mutex_flag); // 条件阻塞,即当x,y<100时,进行挂起等待其他线程的信号
        }
        printf("x 和y已达到要求:%d %d\n", x, y);
        x = 0;
        y = 0;
        pthread_mutex_unlock(&mutex_flag); // 解锁,“释放资源”
        sleep(2);                          // 为保证输出效果进行延时
    }
}

void *task_C(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex_flag); // 上锁,“申请资源”
        x += 50;
        y += 50;						 //申请到资源后对x和y进行操作
        printf("子线程C对x和y进行操作:%d %d\n", x, y);
        if (x > 100 && y > 100)
        {
            pthread_cond_signal(&cond_flag);
        }								   //满足条件后,发送一条信号给子线程B
        pthread_mutex_unlock(&mutex_flag); // 解锁,“释放资源”
        sleep(2);                          // 为方便展示,进行延迟
    }
}

void *task_D(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex_flag); // 同子线程C
        x += 30;
        y += 30;
        printf("子线程C对x和y进行操作:%d %d\n", x, y);
        if (x > 100 && y > 100)
        {
            pthread_cond_signal(&cond_flag);
        }
        sleep(2);                          // 此处与子线程C进行对比,先sleep,再解锁
        pthread_mutex_unlock(&mutex_flag); 
    }
}
int main(int argc, char const *argv[])
{
    // 1.创建子线程
    pthread_t B_tid;
    pthread_create(&B_tid, NULL, task_B, NULL); // 子线程B
    pthread_t C_tid;
    pthread_create(&C_tid, NULL, task_C, NULL); // 子线程C
    pthread_t D_tid;
    pthread_create(&D_tid, NULL, task_D, NULL); // 子线程C

    // 2.主线程结束
    pthread_exit(NULL);
    return 0; // 主线程在上一条语句已经结束,这条语句永远不会执行
}

上述程序仅运行子线程B与C时,输出结果同预期,如下:

子线程C对x和y进行操作:50 50
子线程C对x和y进行操作:100 100
子线程C对x和y进行操作:150 150
x 和y已达到要求:150 150
子线程C对x和y进行操作:50 50
子线程C对x和y进行操作:100 100
子线程C对x和y进行操作:150 150
x 和y已达到要求:150 150
子线程C对x和y进行操作:50 50
子线程C对x和y进行操作:100 100
子线程C对x和y进行操作:150 150
x 和y已达到要求:150 150

上述程序运行子线程B,C与D时,输出结果如下:

子线程C对x和y进行操作:50 50
子线程C对x和y进行操作:80 80
子线程C对x和y进行操作:110 110
子线程C对x和y进行操作:140 140
子线程C对x和y进行操作:170 170
子线程C对x和y进行操作:200 200
子线程C对x和y进行操作:230 230
子线程C对x和y进行操作:260 260
子线程C对x和y进行操作:290 290
子线程C对x和y进行操作:320 320
子线程C对x和y进行操作:350 350
x 和y已达到要求:350 350
子线程C对x和y进行操作:30 30
子线程C对x和y进行操作:60 60
子线程C对x和y进行操作:90 90
子线程C对x和y进行操作:120 120
子线程C对x和y进行操作:150 150

​ 可见,子线程D抢占资源频繁,子线程C一直在等待资源,而子线程B满足条件收到信号以后,也无法抢到共享资源进行输出,并发度不高。因此,为了提高粒度,需要子线程D在对x和y进行操作后立即进行解锁,然后sleep阻塞,让其他线程有机会得到共享资源,提高并发度。

标签:变量,cond,互斥,线程,pthread,mutex,操作,小结
From: https://www.cnblogs.com/cino/p/18226596

相关文章

  • Linux线程之读写锁小结
    读写锁(rwlock)与互斥锁(MutexLock)相比,有如下特点:更加细致的区分了读和写,给共享资源分别上了“读”锁和“写”锁。“写”独占,“读”共享,“写”锁优先级更高如果共享资源在绝大多数情况下是“读”操作,可以提高程序的并发性能。常用的函数原型总结如下:pthread_rwlock_trwlock......
  • 第2章 变量和简单数据类型
            在本章中,你将学习可在Python程序中使用的各种数据,还将学习如何将数据存储到变量中,以及如何在程序中使用这些变量。2.1运行hello_world.py时发生的情况        运行hello_world.py时,Python都做了些什么呢?下面来深入研究一下。实际上,即便是运行简单......
  • 6.s081通关小结
    6.s081通关小结终于是完成6.s081的所有lab了,感慨万千。已经忘了第一次听说这个lab是在什么时候了,只是模模糊糊地感觉是大三。那时的我第一次找到了刷题之外的新方向。但囿于小镇做题家对计算机认识的滞后性,什么Linux、Ubuntu之类的新系统如同一座大山横亘在我于s081之间。又或......
  • 在进程中使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问
    /******************************************************************** author :北极甜虾呦* date :2024/06/01* function:进程中使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问* note :None* CopyRight(c)[email protected]......
  • 熟悉条件量和互斥量的结合使用
    /***************************************************filename:1.c*author:[email protected]*date:2024/05/31*brief:作业:设计一个程序,主线程需要创建2个子线程之后主线程终止,*此时进程中有2个子线程A和B,此时进程中有一个临......
  • 同步互斥——进程篇(一)信号量
    信号量(Semaphores)信号量是一种用于进程间或线程间同步的机制。它可以限制多个进程或线程对共享资源的并发访问,确保资源被安全使用。信号量的核心思想是通过计数来控制访问,计数值表示当前可以访问资源的可用数量。计数器:信号量的核心是一个整数计数器。当计数器大于0时,表......
  • 系统编程练习题----利用条件量和互斥锁,实现两个线程之间的同步与互斥
    目录题目解析代码结果展示题目解析​ 该题主要依靠条件量和互斥锁来实现线程之间的同步与互斥,分析主线程、线程A和线程B的任务如下:主线程:打开LCD屏和触摸屏的硬件文件,并分别存储两个文件的文件描述符,方便后面进行条件判断。开启线程A和线程B。定义并初始化条件量和互斥......
  • 使用条件量和互斥锁实现线程的同步以及临界资源的互斥访问
    /********************************************************************** name :* function:主线程需要创建2个子线程之后主线程终止,此时进程中有2个子线程A和B,此时进程中有一个临界资源fag,子线程A获取触摸屏坐标并判断坐标值是否在LCD屏的左上角,如果坐标范围满足......
  • 进程控制(互斥锁)
    进程控制(互斥锁)目录进程控制(互斥锁)头文件全局变量234验证头文件/********************************************************************** name :* function:主线程需要创建2个子线程之后主线程终止,此时进程中有2个子线程A和B,此时进程中有一个临界资源fag,子线程......
  • 通过互斥锁+条件量的方式实现同步与互斥
    #include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<semaphore.h>#include<unistd.h>#include<string.h>#include<fcntl.h>//forO_CREATandO_EXCL#include<sys/ipc.h>#include<sys/s......