您的位置:首页 > 编程语言

多线程编程(一)——共享数据同步&线程锁

2016-01-20 22:45 351 查看
多线程共享变量会涉及到数据的安全问题。

验证测试程序如下,两个线程共同对一个(非全局)变量操作,根据初始打印可知地址一样,非全局。

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
void *printA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1)
{
*p = 2;
if(5 == *p)
{
printf("=============\n");
}
}
return NULL;
}
void *changeA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1){
*p = 5;
}
return NULL;
}
int main()
{

int a;
a = 1;
int iRet = 0;
iRet = pthread_create(&thread_a_id,NULL,printA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

while(1)
{
sleep(5);
}

}


因为while(1)运转过快,难以人工分辨,所以设置遇到变量值不符合预期时打印即可。

运行结果:

=============
=============
=============
=============
=============
=============

证明线程间数据被互相篡改,不安全。

线程b就不打印了,肯定也会遇到a被改成2的情况。(为了演示更全面,下边的例子会把a和b线程改对等)

解决思路

我目前所用的,通常是数据加锁和变量不共享两种思路,但是数据不共享很局限,不能满足很多需求,比如多线程需要共同维护一个用户哈希表。

所以就需要给数据加锁。

手动加(实际上简陋、不可用的烂)锁:

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
static int i = 0;

void *printA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1)
{
if(i == 0)
{
i = 1;//lock
*p = 2;
if(5 == *p)
{
printf("=============\n");
}
i = 0;//unlock

}
}
return NULL;
}
void *changeA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1){
if(i == 0)
{
i = 1;//lock
*p = 5;
if(2 == *p)
{
printf("*************\n");
}
i = 0;//unlock
}
}
return NULL;
}
int main()
{
int a;
a = 1;
int iRet = 0;
iRet = pthread_create(&thread_a_id,NULL,printA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

while(1)
{
sleep(5);
}

}


结果打印:

=============
*************
=============
*************
*************
*************
=============
*************
*************
*************
*************
=============
*************
=============
=============
*************
*************
=============
=============
*************
*************
=============
=============
*************
=============
=============
一塌糊涂,不管用。

这个程序证明了C语言的语句非原子操作,是可以分解成更多更细的指令的,不是一个真正意义上的原语,所以程序连死锁的机会都没有,各种乱跑。

PS:在所谓的lock操作i = 1;后加一个i等于0的判断,就可以知道i也是发生变化的。

while(1)
{
if(i == 0)
{
i = 1;//lock
if(0 == i)
{
printf("..............\n");
}
*p = 2;
if(5 == *p)
{
printf("=============\n");
}
i = 0;//unlock

}
}

最终方法

手动设的普通变量解决不了安全性问题,怎么办?其实,pthread库里是自带方法了。应该是线程锁吧,有指定的变量pthread_mutex_t和指定操作pthread_mutex_lock()、pthread_mutex_unlock()

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
static int i = 0;
static pthread_mutex_t sMux;
void *printA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1)
{
pthread_mutex_lock(&(sMux));//lock
*p = 2;
if(5 == *p)
{
printf("=============\n");
}

pthread_mutex_unlock(&(sMux));//unlock
}
return NULL;
}
void *changeA(void *pA)
{
printf("%p!\n",pA);
int *p = pA;
while(1){
pthread_mutex_lock(&(sMux));//lock
*p = 5;
if(2 == *p)
{
printf("*************\n");
}

pthread_mutex_unlock(&(sMux));//unlock
}
return NULL;
}
int main()
{
int a;
a = 1;
int iRet = 0;
iRet = pthread_create(&thread_a_id,NULL,printA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
if(iRet != 0)
{
printf("create failed!\n");
}

while(1)
{
sleep(5);
}

}


运行结果

[root@jiaxun multi_thread]# ./a.out
0xbfa82be8!
0xbfa82be8!


不再打印多余信息,说明两个线程互相不干扰,不再有安全性问题。

PS:一个线程锁只能互斥保护一个临界资源,这个临界资源其实也不是固定的,全看lock在哪用,其实是一个锁同时只能锁一处资源。

很多不互斥使用的资源,如果用同一个锁,肯定就尴尬了。这就好比你上锁,把自己的锁锁到别人自行车上了,别人拿不了车。这是不想要的结果。

所以要给不同的临界资源陪不同的锁——多初始化一个锁变量,用那个变量去加锁解锁就好了。

测试代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
pthread_t thread1;
pthread_t thread2;
pthread_mutex_t lock1;
pthread_mutex_t lock2;

void* pThreadFunc(void * pMutex)
{
//先锁
pthread_mutex_lock((pthread_mutex_t *)pMutex);
printf("the thread's id is %lu\n",gettid());
//再睡
printf("lock,sleep\n");
sleep(10);
printf("sleep over,unlock,exit!\n");

pthread_mutex_unlock((pthread_mutex_t *)pMutex);
return NULL;
}
int main(){
int iRet = 0;
void * status;
//初始化线程锁
pthread_mutex_init(&lock1,NULL);
//先多线程
iRet = pthread_create(&thread1, NULL, pThreadFunc, &lock1); //建立服务线程
if(iRet != 0)
{
return iRet;
}
iRet = pthread_create(&thread2, NULL, pThreadFunc, &lock2); //建立服务线程
if(iRet != 0)
{
return iRet;
}

pthread_join(thread1,&status);
pthread_join(thread2,&status);
}
结果:

# ./a.out
the thread's id is 9475
lock,sleep
the thread's id is 9476
lock,sleep
sleep over,unlock,exit!
sleep over,unlock,exit!


# ps -efL | grep ./a.out
root      9474  8507  9474  0    3 22:28 pts/1    00:00:00 ./a.out
root      9474  8507  9475  0    3 22:28 pts/1    00:00:00 ./a.out
root      9474  8507  9476  0    3 22:28 pts/1    00:00:00 ./a.out
root      9479  9447  9479  2    1 22:28 pts/2    00:00:00 grep

如果两个线程传同一个线程锁lock1,就会顺序执行:

# ./a.out
the thread's id is 9534
lock,sleep
sleep over,unlock,exit!
the thread's id is 9533
lock,sleep
sleep over,unlock,exit!
所以,只要区分好那把锁锁哪辆车就行了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息