目录
概述
1 保护对共享变量的访问:互斥量
1.1 认识互斥量
1.2 互斥锁API
1.2.1 互斥锁初始化函数
1.2.2 互斥锁函数
1.2.3 互斥锁变体函数
1.3 互斥锁使用方法
1.4 互斥锁死锁
2 互斥量的应用介绍
2.1 创建与销毁
2.1.1 创建互斥量
2.1.2 销毁互斥量
2.2 加锁和解锁
2.2.1 加锁
2.2.2 解锁
4 使用互斥量的案例
4.1 程序功能介绍
4.2 代码实现
4.3 验证
5 参考文献
概述
本文详细介绍互斥量的相关知识,并介绍和互斥量相关的函数接口,并使用具体的实例来介绍这些接口的使用方法。还编写一个案例,以说明互斥量在实际工程应用中的实用技巧。
1 保护对共享变量的访问:互斥量
1.1 认识互斥量
互斥量(Mutex), 又称为互斥锁, 是一种用来保护临界区的特殊变量, 它可以处于锁定(locked) 状态, 也可以处于解锁(unlocked) 状态:如果互斥锁是锁定的, 就是某个特定的线程正持有这个互斥锁;如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时, 如果某个线程试图获取这个互斥锁, 那么这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时, 如果某个线程试图获取这个互斥锁, 那么这个线程将阻塞在互斥锁的等待队列内。互斥量是最简单也是最有效的线程同步机制。程序可以用它来保护临界区,以获得对排它性资源的访问权。另外,互斥量只能被短时间地持有,使用完临界资源后应立即释放锁。互斥量的总结如下:
1)互斥量的作用:确保同一时刻仅有一个线程可以访问共享资源
2)互斥量的状态:已锁定(lock)和未锁定(unlock)
3)任何一段时间,只有一个线程可以锁定该互斥量
4)一旦线程锁定互斥量,该线程称为该互斥量的所有者,只有所有者才能解锁
1.2 互斥锁API
1.2.1 互斥锁初始化函数
互斥锁初始化函数声明:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数介绍
参数 | 描述 |
---|---|
mutex | mutex 是一个 pthread_mutex_t 类型指针, 指向需要进行初始化操作的互斥锁对象 |
attr | 参数 attr 是一个 pthread_mutexattr_t 类型指针,指向一个 pthread_mutexattr_t 类型对象,该对象用<于定义互斥锁的属性,若将参数 attr 设置为 NULL,则表示将互斥锁的属性设置为默认值,在这种情况下其实就等价于 PTHREAD_MUTEX_INITIALIZER 这种方式初始化,而不同之处在于使用宏不进行错误检查。 |
1.2.2 互斥锁函数
互斥锁初始化之后,处于一个未锁定状态,调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁,而调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁。
互斥锁函数声明:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
1.2.3 互斥锁变体函数
当互斥锁已经被其它线程锁住时,调用 pthread_mutex_lock()函数会被阻塞,直到互斥锁解锁;如果线程不希望被阻塞,可以使用 pthread_mutex_trylock()函数;
调用 pthread_mutex_trylock()函数尝试对互斥锁进行加锁,如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,如果互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。
互斥锁变体函数声明:
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *mutex);
1.3 互斥锁使用方法
1)调用 pthread_mutex_lock()函数对互斥锁进行上锁,如果互斥锁处于未锁定状态,则此次调用会上锁成 功,函数调用将立马返回;
2)如果互斥锁此时已经被其它线程锁定了,那么调用 pthread_mutex_lock()会一直阻塞,直到该互斥锁被解锁,到那时,调用将锁定互斥锁并返回。
3)调用 pthread_mutex_unlock()函数将已经处于锁定状态的互斥锁进行解锁。
4)以下行为均属错误:
-
对处于未锁定状态的互斥锁进行解锁操作;
-
解锁由其它线程锁定的互斥锁。
5)如 果 有 多 个 线 程 处 于 阻 塞 状 态 等 待 互 斥 锁 被 解 锁 , 当 互 斥 锁 被 当 前 锁 定 它 的 线 程 调 用 pthread_mutex_unlock()函数解锁后,这些等待着的线程都会有机会对互斥锁上锁,但无法判断究竟哪个线程会获得这个机会。
1.4 互斥锁死锁
当超过一个线程加锁同一组互斥量,就可能发生死锁。
要避免此类死锁的问题,最简单的方式就是定义互斥锁的层级关系,当多个线程对一组互斥锁操作时,总是应该按照相同的顺序对该组互斥锁进行锁定。譬如在上述场景中,如果两个线程总是先锁定 mutex1 在锁mutex2,死锁就不会出现。有时,互斥锁之间的层级关系逻辑不够清晰,即使是这样,依然可以设计出所有线程都必须遵循的强制层级顺序 。
2 互斥量的应用介绍
2.1 创建与销毁
2.1.1 创建互斥量
pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步前 需要先对它进行初始化,可以用静态或动态的方式对互斥量进行初始化。
(1)静态初始化 对于静态分配的 pthread_mutex_t 变量来说,只要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)动态初始化 对于动态分配或者不使用默认属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。 pthread_mutex_int()函数原型如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
一个创建互斥量的实例:
int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_init(&mylock, NULL))
fprintf(stderr, "Failed to initialize mylock : %s\n", strerror(error));
参数 mutex : 一个指向要初始化的互斥量的指针;
参数 attr : 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。
函数成功返回 0,否则返回一个非 0 的错误码.
错误码列表如下:
错误码 | 出错描述 |
---|---|
EAGAIN | 系统缺乏初始化互斥量所需的非内存资源 |
ENOMEM | 系统缺乏初始化互斥量所需的内存资源 |
EPERM | 调用程序没有适当的优先级 |
建议
静态初始化程序通常比调用 pthread_mutex_init 更有效,而且在任何线程开始执行之前,确保变量被初始化一次。
2.1.2 销毁互斥量
销毁互斥量使用 pthread_mutex_destroy()函数,原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
一个销毁互斥量的实例:
int error;
pthread_mutex_t mylock;
if (error = pthread_mutex_destroy(&mylock))
fprintf(stderr, "Failed to destroy mylock : %s\n", strerror(error));
2.2 加锁和解锁
2.2.1 加锁
加锁: 线程试图锁定互斥量的过程。
pthreads 中有两个试图锁定互斥量的函数,其分别如下:
函数名 | 功能介绍 |
---|---|
pthread_mutex_lock() | 会一直阻塞到互斥量可用为止 |
pthread_mutex_trylock() | 尝试加锁, 通常会立即返回 |
2.2.2 解锁
解锁是线程将互斥量由锁定状态变为解锁状态 ,一个加锁解锁的实例:
void function()
{
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mylock);
//临界区代码
pthread_mutex_unlock(&mylock);
}
4 使用互斥量的案例
4.1 程序功能介绍
使用互斥量来保证多线程同时输出顺序的例子,互斥量能保证只有获取资源的线程打印完, 别的线程才能打印, 从而避免了打印乱序的问题。
4.2 代码实现
创建一个test_thread.c文件,然后编写如下代码:
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : test_thread.c
作者 : tangmingfei2013@126.com
版本 : V1.0
描述 : pthread API test
其他 : 无
日志 : 初版V1.0 2024/03/04
***************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
pthread_t tid[2];
pthread_mutex_t lock;
void* thread_forMutex(void *arg)
{
int id = (long)arg;
int i = 0;
pthread_mutex_lock(&lock); // 使用互斥量保护临界区
printf("hread %d started\n", id);
for (i = 0; i < 5; i++) {
printf("hread %d printing\n", id);
usleep(10);
}
printf("hread %d finished\n", id);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(void)
{
long i = 0;
int err;
if (pthread_mutex_init(&lock, NULL) != 0) // 动态初始化互斥量
{
printf("\n Mutex init failed\n");
return 1;
}
while(i < 2){
err = pthread_create(&(tid[i]), NULL, &thread_forMutex, (void*)i);
if (err != 0){
printf("Can't create thread :[%s]", strerror(err));
}
i++;
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&lock);
return 0;
}
4.3 验证
编译代码,然后在板卡中运行。可以看到 thread -1 先获取互斥锁并进行打印, thread -1 打印完成后, thread -0才开始打印。
5 参考文献
-
《现代操作系统》
-
《linux/unix系统编程手册》