一、前言
前面我们了解了线程的基础知识,而在多线程编程中,线程同步是核心技术,用于解决多线程并发访问共享资源时的竞态条件,保证数据一致性和线程执行顺序的可控性;互斥锁就是线程同步的其中一种机制。
二、线程同步
2.1、线程同步的定义
在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
线程共享进程的地址空间(全局变量、堆、文件描述符等),当多个线程同时读写「临界资源」(如全局变量、硬件设备、网络连接)时,会导致数据错乱。
同步的目标:1、保护临界资源,同一时间只有一个线程访问;2、协调线程执行顺序(如生产者生产完数据后,消费者再消费)。
补:临界区:访问临界资源的代码段(需被同步机制保护);竞态条件:多线程并发执行临界区代码,导致结果依赖于线程执行顺序的不可控问题;同步机制:通过内核 / 库提供的接口,限制临界区的并发访问、协调线程执行时机。
2.2、同步的目标
1、原子性:保证临界区代码 “要么全执行,要么全不执行”,不可中断;
2、可见性:一个线程修改的共享变量,其他线程能立即看到;
3、有序性:保证线程按预期的顺序执行(如生产者先生产,消费者后消费)。
2.3、典型示例
首先创建一个pthread_tb.c文件,然后输入以下代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number;//共享全局变量
void *myfun1(void *arg)
{
for(int i=0;i<10000;i++)
{
int ret;
ret = number;
ret++;
number = ret;
printf("fun1 is %ld,number is %d\n",pthread_self(),number);
usleep(10);//微秒级睡眠
}
}
void *myfun2(void *arg)
{
for(int i=0;i<10000;i++)
{
int ret;
ret = number;
ret++;
number = ret;
printf("fun2 is %ld,number is %d\n",pthread_self(),number);
usleep(10);
}
}
int main()
{
pthread_t pthid1;
pthread_t pthid2;
pthread_create(&pthid1,NULL,myfun1,NULL);//线程创建
pthread_create(&pthid2,NULL,myfun2,NULL);
pthread_join(pthid1,NULL);//线程等待
pthread_join(pthid2,NULL);
return 0;
}
编译并运行,结果如下:

看到这里可能会有疑问,理论上运行完后number会运行到20000,那为什么最终只有19803呢?
因为理论结果前提是 20000 次自增都能被正确累加,但你这段代码里的“自增”并不是一条不可分割的操作,而是读+加+写三步,两个线程一旦交叉执行,就会发生丢失更新,导致很多次“+1”被白白覆盖掉,假设某一刻number=19780,线程1执行到ret = number;读到ret = 19780(但还没写回),发生线程切换,线程也执行到:ret = number,也读到ret=19780,线程2,ret++;number=ret,写回number =19781(这次+1生效),再切回线程1,线程1:ret++; number=ret; 也写回 number=19781(把线程2的结果“覆盖成同一个值”);这两次自增,本该让 number 变成 19782,结果只变成 19781 —— 少加了 1。这种“两个线程读到同一个旧值,然后分别写回同一个新值”的情况在你循环 20000 次里会发生很多很多次,于是最终就会出现 19803 < 20000。以上例子就是典型的反面案例。
三、互斥锁
3.1、互斥锁的定义
互斥锁是 Linux 多线程同步中最基础、最常用的核心机制,其核心目标是保证同一时间只有一个线程能进入 “临界区”(访问共享资源的代码段),从而解决 “竞态条件”,保证共享资源的原子性、可见性和有序性。
互斥锁本质是一个“二值锁”,状态只有未锁定与已锁定。通过 “加锁 - 访问临界区 - 解锁” 的闭环,强制临界区代码原子执行(要么全执行,要么全不执行,不可被线程切换中断)。
3.2、Linux互斥锁的底层实现
1、用户态尝试加锁:线程加锁时,先通过原子操作尝试获取锁(修改锁的状态);
2、成功则直接执行:若锁未被持有,加锁成功,直接进入临界区;
3、失败则内核态挂起:若锁已被持有,线程进入内核态的 “等待队列” 挂起(放弃 CPU),避免无意义的自旋;
4、解锁时唤醒线程:持有锁的线程解锁时,若等待队列有线程,内核会唤醒其中一个线程重新尝试加锁。
补:原子操作是指一个操作在执行过程中不可被中断、不可被其他线程打断,从而保证对共享数据的修改要么完整发生、要么完全不发生。
3.3、互斥锁的相关函数
1、静态初始化互斥锁
函数原型如下:
#include <pthread.h>
// 静态初始化全局互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
注:静态初始化的互斥锁无需手动销毁;同一互斥锁不能重复初始化(已初始化的锁再次调用 pthread_mutex_init 会导致未定义行为)。
2、动态初始化互斥锁
函数原型如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:mutex:指向要初始化的互斥锁对象(不能为 NULL);
attr:互斥锁属性(NULL 表示使用默认属性);
返回值:成功:返回0;失败:非0错误码。
3、阻塞加锁
函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:mutex:指向已初始化的互斥锁对象。
返回值:成功:0(获取到锁);失败:非0错误码。
功能:若锁未被持有,当前线程立即获取锁,锁状态变为已锁定;若锁已被持有,当前线程放弃 CPU 使用权,进入内核态等待队列,直到锁被释放。
4、非阻塞加锁
函数原型如下:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数:mutex:指向已初始化的互斥锁对象。
返回值:成功:0 (获取到锁);失败:EBUSY(锁已被持有),EINVAL(锁未初始化)等。
功能:若锁未被持有,立即获取并返回 0;若锁已被持有,不阻塞,直接返回 EBUSY 错误,线程可执行其他逻辑。
适用场景:不想让线程阻塞等待锁的场景。
5、解锁
函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:mutex:指向已初始化的互斥锁对象。
返回值:成功:0;失败:非 0 错误码。
功能:释放当前线程持有的锁
注:只有持有锁的线程能解锁,解锁未加锁的锁会触发错误。
6、销毁互斥锁
函数原型如下:
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
功能:销毁属性对象,释放其占用的资源(必须与pthread_mutex_init 配对使用)。
3.4、典型示例
接上一个线程同步的反面案例,通过互斥锁的形式来实现线程的同步,具体代码如下所示:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number;
pthread_mutex_t mutex;
void *myfun1(void *arg)
{
for(int i=0;i<10000;i++)
{
//lock
pthread_mutex_lock(&mutex);
int ret;
ret = number;
ret++;
number = ret;
printf("fun1 is %ld,number is %d\n",pthread_self(),number);
//ulock
pthread_mutex_unlock(&mutex);
usleep(10);
}
}
void *myfun2(void *arg)
{
for(int i=0;i<10000;i++)
{
//lock
pthread_mutex_lock(&mutex);
int ret;
ret = number;
ret++;
number = ret;
printf("fun2 is %ld,number is %d\n",pthread_self(),number);
//ulock
pthread_mutex_unlock(&mutex);
usleep(10);
}
}
int main()
{
//init mutex
pthread_mutex_init(&mutex,NULL);
pthread_t pthid1;
pthread_t pthid2;
pthread_create(&pthid1,NULL,myfun1,NULL);
pthread_create(&pthid2,NULL,myfun2,NULL);
pthread_join(pthid1,NULL);
pthread_join(pthid2,NULL);
//kill mutex
pthread_mutex_destroy(&mutex);
return 0;
}
编译并运行,结果如下:
可以看到通过互斥锁来实现了线程的同步,避免了线程之间的竞争关系。

被折叠的 条评论
为什么被折叠?



