解决多线程访问临界区的冲突问题,最常用的方法就是利用互斥量mutex了。linux中互斥的使用分为以下步骤:
互斥量的本质其实就是一个结构体,请参照/usr/include/bits/pthreadtypes.h中对pthread_mutex_t的定义:
从他的成员可以大概猜到其作用的原理,__owner记录着当前拥有互斥的线程的id,而其他线程企图访问加锁的内容时,互斥量会用打算访问的线程的id和__owner进行比较,若不相同,则阻止该线程的访问。
互斥量的初始化函数pthread_mutext_init()的原型如下:
第一个参数为我们定义的互斥量的指针,第二个参数表示设置互斥的属性值,通常情况下为NULL即可。
这段代码放到临界区之前,加上之后别的线程就无法访问临界区内的代码,就只能等待互斥解锁。
可能有的童鞋会有疑问:上述代码的临界区区不是:num++和num--吗?为什么没把锁直接加到临界区外面,而是放到了for循环外?不知道大家有没有感觉到上面的程序运行时,明显比加锁之前慢了很多,至少在我的虚拟机上是可以感知到的。因为加锁是有性能损耗的,如果我们在循环中加锁,会造成反反复复的上锁、解锁......,会造成极大的性能所示,那样就得不偿失了。所以:
1、定义互斥量。
pthread_mutex_t mutex;
互斥量的本质其实就是一个结构体,请参照/usr/include/bits/pthreadtypes.h中对pthread_mutex_t的定义:
typedef union
{
struct __pthread_mutex_s
{
int __lock;
unsigned int __count;
int __owner;
/* KIND must stay at this position in the structure to maintain
binary compatibility. */
int __kind;
unsigned int __nusers;
__extension__ union
{
int __spins;
__pthread_slist_t __list;
};
} __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
从他的成员可以大概猜到其作用的原理,__owner记录着当前拥有互斥的线程的id,而其他线程企图访问加锁的内容时,互斥量会用打算访问的线程的id和__owner进行比较,若不相同,则阻止该线程的访问。
2、互斥量的初始化.
pthread_mutex_init(&mutex,NULL);
互斥量的初始化函数pthread_mutext_init()的原型如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
第一个参数为我们定义的互斥量的指针,第二个参数表示设置互斥的属性值,通常情况下为NULL即可。
3、互斥量的加锁。
pthread_mutex_lock(&mutex);
这段代码放到临界区之前,加上之后别的线程就无法访问临界区内的代码,就只能等待互斥解锁。
4、互斥量解锁。
pthread_mutex_unlock(&mutex);
5、销毁互斥
pthread_mutex_destory(&mutex);
现在我们把上一片专栏文章中提到的存在临界区问题的程序加上互斥锁:
#include<stdio.h>
#include<pthread.h>
int num=0;
pthread_mutex_t mutex;
void* pthread_main1()
{
int i=0;
pthread_mutex_lock(&mutex);
for(i=0;i<1000000000;i++)
{
--num;
}
pthread_mutex_unlock(&mutex);
}
void* pthread_main2()
{
int i=0;
pthread_mutex_lock(&mutex);
for(i=0;i<1000000000;i++)
{
++num;
}
pthread_mutex_unlock(&mutex);
}
int main()
{
int i=0;
pthread_mutex_init(&mutex,NULL);
printf("mutex=%d\n",mutex);
pthread_t pid[2];
pthread_create((void*)&(pid[0]),NULL,(void*)pthread_main1,NULL);
pthread_create((void*)&(pid[1]),NULL,(void*)pthread_main2,NULL);
for(i=0;i<2;i++)
{
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
pthread_mutex_destroy(&mutex);
return 0;
}
运行程序,结果如下,不再出现问题:
[Hyman@Hyman-PC multithread]$ gcc ts3.c -lpthread
[Hyman@Hyman-PC multithread]$ ./a.out
0
可能有的童鞋会有疑问:上述代码的临界区区不是:num++和num--吗?为什么没把锁直接加到临界区外面,而是放到了for循环外?不知道大家有没有感觉到上面的程序运行时,明显比加锁之前慢了很多,至少在我的虚拟机上是可以感知到的。因为加锁是有性能损耗的,如果我们在循环中加锁,会造成反反复复的上锁、解锁......,会造成极大的性能所示,那样就得不偿失了。所以:
“尽可能不要在循环中加锁!”
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL38