互斥锁的使用

简介: 互斥锁的使用

线程通信笔记中所展示的代码中,线程在访问共享的全局变量时,没有按照一定的规则顺序进行访问造成了不可预计的后果。针对代码的运行结果分析,其原因就是线程在访问共享资源的过程中被其他线程打断,其他线程也开始访问共享资源导致了数据的不确定性。对于上述情况而言,最好的解决办法是当一个线程在进行共享资源的访问时,其他线程不能访问,保证对于共享资源操作的完整性。


本笔记介绍一种互斥机制,用以保护对共享资源的操作,即保护线程对共享资源的操作代码可以完整执行,而不会在访问的中途被其他线程介入对共享资源访问,造成错误。在这里,通常把对共享资源操作的代码段,称之为临界区,其共享资源也可以称为临界资源。于是这种机制——互斥锁的工作原理就是对临界区进行加锁,保证处于临界区的线程不被其他线程打断,确保其临界区运行完整。


互斥锁 是一种互斥机制。互斥锁作为一种资源,在使用之前需要先初始化一个互斥锁。每一个线程在访问共享资源时,都需要进行加锁操作,如果线程加锁成功,则可以访问共享资源,期间不会被打断,在访问结束之后解锁。如果线程在进行上锁时,其锁资源被其他线程持有,那么该线程则会执行阻塞等待,等待锁资源被解除之后,才可以进行加锁。对于多线程而言,在同等条件下,对互斥锁的持有是不确定的,先持有锁的线程先访问,其他线程只能阻塞等待。也就是说,互斥锁并不能保证线程的执行先后,但却可以保证对共享资源操作的完整性。如下图所示:

微信截图_20221209154150.png


互斥锁的使用包括初始化互斥锁互斥锁上锁互斥锁解锁互斥锁释放

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);点击复制复制失败已复制


pthread_mutex_init() 函数用来实现互斥锁的初始化。参数 mutex 用来指定互斥锁的标识符,类似于 ID ;参数 attr 为互斥锁的属性,一般设置为 NULL ,即默认属性。与之相反 pthread_mutex_destroy() 函数为释放互斥锁,参数 mutex 用来指定互斥锁的标识符。只有当互斥锁未处于锁定状态,且后续也无任何线程企图锁定它时,将其摧毁才是安全的。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);点击复制复制失败已复制


初始化之后,互斥锁处于未锁定状态, pthread_mutex_lock() 函数为上锁处理,如果该锁资源处于持有状态,那么函数将直接导致线程阻塞。直到其他线程使用 pthread_mutex_unlock() 函数进行解锁,参数 mutex 为互斥锁的标识符。


注意

不可对处于未锁定状态的互斥量进行解锁,或者解锁由其他线程锁定的互斥锁


之前的线程通信笔记中所展示的代码就可以使用互斥锁来实现互斥操作,避免竞态,只需要使用互斥锁将线程的临界区锁住即可,具体代码如下所示:

#include <pthread.h>
#include <stdio.h>
#define errlog(errmsg)                                                \
    do {                                                              \
        perror(errmsg);                                               \
        printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \
        return -1;                                                    \
    } while (0)
int value1 = 0;
int value2 = 0;
int count = 0;
pthread_mutex_t lock;
void *thread1_handler(void *arg) {
    while (1) {
    pthread_mutex_lock(&lock);
        value1 = count;
        value2 = count;
        count++;
    pthread_mutex_unlock(&lock);
    }
    pthread_exit("thread1...exit");
}
void *thread2_handler(void *arg) {
    while (1) {
    pthread_mutex_lock(&lock);
        if (value1 != value2) {
            sleep(1);
            printf("value1 = %d value2 = %d\n", value1, value2);
        }
    pthread_mutex_unlock(&lock);
    }
    pthread_exit("thread2...exit");
}
int main(int argc, const char *argv[]) {
    pthread_t thread1, thread2;
    void *retval;
  if(pthread_mutex_init(&lock,NULL)!=0){
    errlog("pthread_mutex_init error");
  }
    if (pthread_create(&thread1, NULL, thread1_handler, NULL) != 0) {
        errlog("pthread_create1 error");
    }
    if (pthread_create(&thread2, NULL, thread2_handler, NULL) != 0) {
        errlog("pthread_create2 error");
    }
    pthread_join(thread1, &retval);
    printf("%s\n", (char *)retval);
    pthread_join(thread2, &retval);
    printf("%s\n", (char *)retval);
  pthread_mutex_destroy(&lock);
    return 0;
}点击复制复制失败已复制


编译并运行,可以看出,程序并不会执行任何输出,因为不论哪一个线程得到互斥锁,进入自己的临界区,另外一个线程只能阻塞。因此判定条件永远不会成立。这是互斥锁介入之后代码的正确执行结果。


同时,在上述代码中还需要注意的是,如果多线程同时对一个共享资源进行访问,其中一个线程采用了互斥锁的机制,其他线程则必须也遵循该规则,即使用互斥锁机制;如果有任何一个线程在访问共享资源的时候违背了规则,那么结果将会是不可预计的。

Pthread API 还提供了 pthread_mutex_lock() 函数的两个版本:

pthread_mutex_trylock()pthread_mutex_timedlock()

#include <pthread.h>
#include <time.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);点击复制复制失败已复制


如果互斥锁被持有(占用),对其执行函数 pthread_mutex_trylock() 会失败并返回错误码EBUSY ,而不会执行睡眠等待,除此之外与 pthread_mutex_lock() 函数一致。 pthread_mutex_timedlock() 函数可以指定一个附件的参数 abs_timeout ,用以设置线程等待的时间期限。如果该线程等待的期限时间已到,然而互斥锁仍然处于被持有状态,那么 pthread_mutex_timedlock() 函数返回错误码 ETIMEOUT 。除此之外,其功能与 pthread_mutex_lock() 函数一致。


pthread_mutex_trylock() 函数和 pthread_mutex_timedlock() 函数使用的频率相对于 pthread_mutex_lock() 函数要少。在大多数程序中,线程对互斥锁的持有时间应尽可能短,以避免其他线程等待时间太久,保证其他线程可以尽快获得互斥锁。如果某一线程使用 pthread_mutex_trylock() 函数周期性的轮询是否可以占有互斥锁,则增加了系统消耗。

目录
相关文章
|
2月前
|
Ubuntu Linux
Ubuntu启动提示"recovering journal"并进入紧急模式。
若您对Linux系统不太熟悉,建议寻求有经验的技术人员帮助。在大多数情况下,这些步骤将足以帮助您诊断问题,并可能恢复系统到正常工作状态,但是在极端情况下,系统可能无法修复,那时就需要考虑恢复数据和重新安装Ubuntu。所以,在日常使用中定时备份数据是非常重要的。这样可以在遇到系统崩溃时降低数据丢失的风险。
243 0
|
8月前
|
存储 人工智能 安全
阿里云服务器五代、六代、七代、八代实例简介及性能提升介绍参考
随着技术的不断进步,阿里云服务器实例也经历了多代升级,从五代实例到最新的八代实例,每一代都在性能、稳定性、能效比等方面取得了显著提升。有的用户由于是初次接触阿里云服务器,所以不是很清楚阿里云服务器五代、六代、七代、八代实例有哪些,它们各自在云服务器性能上有哪些提升。本文将详细介绍阿里云服务器五代、六代、七代、八代实例的特点及性能提升,以供了解及选择。
287 10
阿里云服务器五代、六代、七代、八代实例简介及性能提升介绍参考
|
安全 API Windows
CreateMutex用法
CreateMutex用法
VSCode设置折叠左侧资源管理器所有文件夹的快捷键Alt+X、切换左侧活动栏显示隐藏快捷键Alt+Q
VSCode设置折叠左侧资源管理器所有文件夹的快捷键Alt+X、切换左侧活动栏显示隐藏快捷键Alt+Q
|
Unix Shell Linux
linux互斥锁(pthread_mutex)知识点总结
linux互斥锁(pthread_mutex)知识点总结
|
Unix Linux
fcntl()函数的作用及用法
fcntl()函数的作用及用法
495 0
|
网络协议 算法 安全
|
机器学习/深度学习 人工智能 自然语言处理
【JAVA】AI医疗导诊系统源码
智慧导诊 患者可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
278 1
[simulink] --- simulink中stateflow的使用
[simulink] --- simulink中stateflow的使用
1194 0
|
存储 Java Linux
【Linux】基础IO(一) :文件描述符,文件流指针,重定向(上)
【Linux】基础IO(一) :文件描述符,文件流指针,重定向
299 0