线程同步:
线程同步指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。保证了数据的一致性。举个简单的例子就是,当一个线程将全局变量var=100加上10,另外一个线程时将var乘以2,这样最后得出的结果因为cpu不同的调度策略导致不同的结果,可能是220也可能是210还可能是110或者200,220和210可以理解,无非就是一个先操作一个后操作,但是110和200呢,这是因为两个线程读取到var的值为100时,都进行了操作,导致另外一个线程运行出来的结果被覆盖了,为了得到我们想要的结果,不让数据出现混乱,就需要进行同步。
造成数据混乱的原因:
1.资源共享
2.随机调度
3.缺乏同步机制
前两点我们无法改变,而第三点就是解决数据混乱的关键。这里就引入了锁机制。
互斥锁
相关概念:
Linux提供互斥锁mutex(又称互斥量)。每个线程在对资源进行操作前都尝试先加锁,成功加锁了之后才能操作该资源,操作结束后就解锁。在同一时间,锁只有一把,如果线程A加锁正在访问资源,这时B尝试加锁,就会阻塞。但是互斥锁有个特点,就是不加锁也可以访问数据,比如之前的线程A加锁了正在访问资源,这时B不加锁也可以直接访问数据。所以互斥锁实质上是操作系统提供的一把”建议锁”,没有进行强制限定必须有锁才能访问。
相关函数:
初始化互斥锁:
函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
返回值:成功返回0,失败返回错误号
参数:mutex:传出参数,代表互斥锁;attr:传入参数,代表互斥锁属性,使用默认属性(线程间共享)传NULL。pthread_mutex_t变量类型是一个结构体,可以简单理解成整数,只有1或者0两个取值,初始值为0。
销毁互斥锁:
函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
参数:mutex:传出参数,要销毁的互斥锁。
加锁:
函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号。
参数:mutex:传出参数,要加锁的互斥锁。
可理解为将mutex–。如果已有线程进行了加锁,那么就会阻塞等待,直到持有该互斥锁的其它进程解锁为止。
尝试加锁:
函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号。
参数:mutex:传出参数,要尝试加锁的互斥锁。如果已有线程进行了加锁,不会阻塞,直接返回错误号。
解锁:
函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
参数:mutex:传出参数,要解锁的互斥锁。
可理解为mutex++。当解锁成功时,该函数会把阻塞在该锁上的所有线程全部唤醒,之后哪个线程抢到锁继续执行,就是调度优先级的问题了,默认是先阻塞的先唤醒。
继续引用开头那个例子,引用互斥锁的机制就可以避免200和110这样的答案出现。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
int var = 100;
pthread_mutex_t mutex; //定义互斥锁变量mutex
void *test1(void *arg)
{
pthread_mutex_lock(&mutex); //加锁
var += 10;
pthread_mutex_unlock(&mutex); //解锁
return NULL;
}
void *test2(void *arg)
{
pthread_mutex_lock(