【Linux C | 多线程编程】线程同步 | 互斥量(互斥锁)介绍和使用
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:
本文未经允许,不得转发!!!
🎄一、概述
互斥量采用的是英文mutual exclusive(互相排斥之意)的缩写,即mutex
,是多线程编程中,常用来进行同步访问的方式之一。根据互斥量的用法,可以形象地将互斥量比喻成一把锁,锁住关键代码(临界区),每次只允许一个线程进入。
互斥量的工作机制:互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所以在该互斥锁上的阻塞线程都会变成可进行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程在次被阻塞,等待下次运行状态。
🎄二、为什么需要互斥量
大部分情况下, 线程使用的数据都是局部变量, 变量的地址在线程栈空间内, 这种情况下, 变量归属于单个线程, 其他线程无法获取到这种变量。但多数的多线程编程种,会出现一些资源是多个线程共享使用的,如:全局变量、堆空间指针变量等。
当多个线程可以同时改变某个共享资源,并且这个改变的操作不是原子操作,而又不加限制的话,那么改变的结果可能是意想不到的。这就是需要互斥量的原因。
🌰看例子:下面例子,创建4个线程,对共享资源(g_Count
全局变量)执行了1000万次自加1操作。我们期待的结果应该是4000万,但运行结果有时却非4000万。
// 08_mutex_test.c
// gcc 08_mutex_test.c -lpthread
#include <stdio.h>
#include <pthread.h>
#include <sys/syscall.h>
int g_Count = 0;
void *func(void *arg)
{
int i=0;
for(i=0; i<10000000; i++)
{
g_Count++;
}
return NULL;
}
int main()
{
// 创建4个线程
pthread_t threadId[4];
int i=0;
for(i=0; i<4; i++)
{
pthread_create(&threadId[i], NULL, func, NULL);
}
for(i=0; i<4; i++)
{
pthread_join(threadId[i],NULL);
printf("join threadId=%lx\n",threadId[i]);
}
printf("g_Count=%d\n",g_Count);
return 0;
}
运行结果如下:执行三次,只出现了一次4000万,且每次结果都不一样。因为g_Count++;
语句不是一个原子操作,假设4个线程同时获取到g_Count
时值为1,4个线程都执行g_Count++
后,每个线程都认为此时g_Count
的值为2,但4个线程执行了4次了。
综上所述,当多个线程可以同时操作共享资源时,需要满足下面三点来使各个线程互斥:
1、当一个线程操作共享资源时,不允许其他线程同时操作该资源。
2、当线程不再操作共享资源时,不能阻碍其他线程操作该资源。
3、当多个线程同时操作一个共享资源时,只允许一个线程执行操作。
🎄三、互斥量的使用
正确地使用互斥量来保护共享数据,首先要定义和初始化互斥量。然后是使用互斥量的加锁、解锁来保护共享数据,最后使用完销毁互斥量。
✨3.1 互斥量的初始化
POSIX提供了两种初始化互斥量的方法。
-
1、是将
PTHREAD_MUTEX_INITIALIZER
赋值给定义的互斥量,如下:#include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
但这个方法没办法设置互斥量的属性,也不适用于动态分配的互斥量,比较少用。
-
2、使用
pthread_mutex_init
初始化互斥量。如下:#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
第二个
pthread_mutexattr_t
指针的入参,是用来设定互斥量的属性的。大部分情况下,并不需要设置互斥量的属性,传递NULL即可,表示使用互斥量的默认属性。
调用pthread_mutex_init
之后, 互斥量处于没有加锁的状态。
✨3.2 互斥量的销毁
使用pthread_mutex_init
初始化的互斥量,在确定不再需要互斥量的时候, 就要销毁它。 在销毁之前, 有三点需要注意:
1、使用PTHREAD_MUTEX_INITIALIZER
初始化的互斥量无须销毁。
2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。
3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。
销毁互斥量的接口如下:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
当互斥量处于已加锁的状态, 或者正在和条件变量配合使用, 调用pthread_mutex_destroy函数会返回EBUSY
错误码。
✨3.3 互斥量的加锁和解锁
关于互斥量的加锁和解锁,POSIX提供了如下接口:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock (pthread_mutex_t * mutex, const struct timespec *abstime);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- pthread_mutex_lock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则一直阻塞等待互斥量解锁;
- pthread_mutex_trylock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则不会阻塞等待,直接返回
EBUSY
; - pthread_mutex_timedlock:对互斥量加锁,如果互斥量未锁定,则将互斥量锁定, 同时返回成功。如果互斥量已经上锁,则等待
abstime
设置的时间,如果还处于锁定状态,直接返回ETIMEOUT
;注意,abstime是绝对时间,如果最多等待2分钟, 那么这个值应该是当前时间加上2分钟 - pthread_mutex_unlock:对互斥量解锁。
🎄四、互斥量的属性
线程和线程的同步对象(互斥量,读写锁,条件变量)都具有属性。在修改属性前都需要对该结构进行初始化。使用后要把该结构回收。大部分情况使用的都是默认属性。
互斥量的属性相关接口:
int pthread_mutexattr_init (pthread_mutexattr_t *attr); // 初始化互斥量属性为默认属性
int pthread_mutexattr_destroy (pthread_mutexattr_t *attr);// 销毁互斥量属性
/* Get the process-shared flag of the mutex attribute ATTR. */
int pthread_mutexattr_getpshared (const pthread_mutexattr_t * attr,int *pshared);
/* Set the process-shared flag of the mutex attribute ATTR. */
int pthread_mutexattr_setpshared (pthread_mutexattr_t *attr, int shared)
attr
中pshared
属性表示用这个属性对象创建的互斥锁的作用域,它的取值可以是PTHREAD_PROCESS_PRIVATE
或PTHREAD_PROCESS_SHARED
。
- PTHREAD_PROCESS_PRIVATE:默认属性,只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁;
- PTHREAD_PROCESS_SHARED:所创建的互斥锁将被保存在共享内存中,可以被多个进程中的线程共享。
互斥锁类型:
- PTHREAD_MUTEX_NORMAL;
- PTHREAD_MUTEX_ERRORCHECK;
- PTHREAD_MUTEX_RECURSIVE;
- PTHREAD_MUTEX_DEFAULT。
🎄五、总结
本文介绍了Linux系统下,多线程编程常用的互斥量,先是介绍了需要互斥量的原因,然后介绍互斥量的使用,并给出使用例子。
下面是使用互斥量对第二小节例子进行修改后的代码:
// 08_mutex_test.c
// gcc 08_mutex_test.c -lpthread
#include <stdio.h>
#include <pthread.h>
#include <sys/syscall.h>
int g_Count = 0;
pthread_mutex_t g_mutex;
void *func(void *arg)
{
int i=0;
for(i=0; i<10000000; i++)
{
pthread_mutex_lock(&g_mutex);
g_Count++;
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
int main()
{
pthread_mutex_init(&g_mutex, NULL);
// 创建4个线程
pthread_t threadId[4];
int i=0;
for(i=0; i<4; i++)
{
pthread_create(&threadId[i], NULL, func, NULL);
}
for(i=0; i<4; 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;
}
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁
参考:
《linux_多线程》
《Linux环境编程:从应用到内核》
原文地址:https://blog.csdn.net/wkd_007/article/details/137504491
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!