多线程共享变量会涉及到数据的安全问题。
验证测试程序如下,两个线程共同对一个(非全局)变量操作,根据初始打印可知地址一样,非全局。
#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); } }
运行结果:
============= ============= ============= ============= ============= =============
证明线程间数据被互相篡改,不安全。
线程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
# ./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!所以,只要 区分好那把锁锁哪辆车就行了。