当不同线程同时访问同一个数据时,如果不加任何处理的话,就可能出现数据错乱。用一个例子说明:
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
int num = 10;
void* call_back1(void* arg)
{
num += 1;
/* 假设这里执行了很多语句,用了一段时间 */
sleep(2);
printf("thread1: num=%d\n",num);
return NULL;
}
void* call_back2(void* arg)
{
/* 让线程1先改变 num 的值 */
sleep(1);
num -= 1;
printf("thread2: num=%d\n",num);
return NULL;
}
int main()
{
pthread_t pthrd1,pthrd2;
pthread_create(&pthrd1,NULL,call_back1,NULL);
pthread_create(&pthrd2,NULL,call_back2,NULL);
pthread_join(pthrd1,NULL);
pthread_join(pthrd2,NULL);
printf("main: num=%d\n",num);
return 0;
}
程序的本意是线程一对 num 执行加一操作并输出 num,线程二对 num 进行减一操作并输出 num。然而由于线程以加一过后执行了很多其他操作后才输出 num,但是在输出之前,线程二已经对 num 的值进行了减一操作,所以线程一输出的值是 10(10 --- 11 --- 10).
lingyun@manjaro ~/Document/CppCode/study gcc study.cpp -o study -lpthread -g
lingyun@manjaro ~/Document/CppCode/study ./study
thread2: num=10
thread1: num=10
main: num=10
lingyun@manjaro ~/Document/CppCode/study
可见,如果不做任何处理,就会出现数据错乱的情况。我们需要用互斥锁来解决这个问题,互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。
互斥锁的操作流程如下:
- 在访问共享资源后临界区域前,对互斥锁进行加锁。
- 在访问完成后释放互斥锁导上的锁。
- 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
函数说明
#include<pthread.h>
/* 1. 初始化一个互斥锁 */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
/* 参数:
mutex:互斥锁地址。类型是 pthread_mutex_t 。
attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
*/
/* 可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/* 这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。*/
/*
返回值:
成功:0,成功申请的锁默认是打开的。
失败:非 0 错误码
*/
/* 2. 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,直到互斥锁解锁后再上锁。 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
/* 参数:
mutex:互斥锁地址。
*/
/* 返回值:
成功:0
失败:非 0 错误码
*/
/* 调用该函数时,若互斥锁未加锁,则上锁,返回 0;若互斥锁已加锁,则函数直接返回失败,即 EBUSY。*/
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/* 3. 对指定的互斥锁解锁。 */
int pthread_mutex_unlock(pthread_mutex_t * mutex);
/* 参数:
mutex:互斥锁地址。
*/
/* 返回值:
成功:0
失败:非 0 错误码
*/
/* 4. 销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/* 参数:
mutex:互斥锁地址。
*/
/* 返回值:
成功:0
失败:非 0 错误码
*/
用互斥锁优化上面的例子:
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
int num = 10;
/* 定义一个互斥锁 */
pthread_mutex_t mutex;
void* call_back1(void* arg)
{
/* 上锁 */
pthread_mutex_lock(&mutex);
num += 1;
/* 假设这里执行了很多语句,用了一段时间 */
sleep(2);
printf("thread1: num=%d\n",num);
/* 解锁 */
pthread_mutex_unlock(&mutex);
return NULL;
}
void* call_back2(void* arg)
{
/* 让线程1先改变 num 的值 */
sleep(1);
/* 上锁 */
pthread_mutex_lock(&mutex);
num -= 1;
printf("thread2: num=%d\n",num);
/* 解锁 */
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t pthrd1,pthrd2;
/* 初始化互斥锁的两种方式 */
mutex = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_init(&mutex,NULL);
pthread_create(&pthrd1,NULL,call_back1,NULL);
pthread_create(&pthrd2,NULL,call_back2,NULL);
pthread_join(pthrd1,NULL);
pthread_join(pthrd2,NULL);
printf("main: num=%d\n",num);
/* 销毁互斥锁 */
pthread_mutex_destroy(&mutex);
return 0;
}
执行可见,这次线程一正长输出了加一后的 num 值 11。当线程一执行 pthread_mutex_lock(&mutex) 后,线程二执行到 pthread_mutex_lock(&mutex) 时就会阻塞。等待线程一执行pthread_mutex_unlock(&mutex) 后才能继续执行。
lingyun@manjaro ~/Document/CppCode/study gcc study.cpp -o study -lpthread -g
lingyun@manjaro ~/Document/CppCode/study ./study
thread1: num=11
thread2: num=10
main: num=10
lingyun@manjaro ~/Document/CppCode/study