多线程编程(一)——共享数据同步&线程锁
2016-01-20 22:45
351 查看
多线程共享变量会涉及到数据的安全问题。
验证测试程序如下,两个线程共同对一个(非全局)变量操作,根据初始打印可知地址一样,非全局。
因为while(1)运转过快,难以人工分辨,所以设置遇到变量值不符合预期时打印即可。
运行结果:
证明线程间数据被互相篡改,不安全。
线程b就不打印了,肯定也会遇到a被改成2的情况。(为了演示更全面,下边的例子会把a和b线程改对等)
解决思路
我目前所用的,通常是数据加锁和变量不共享两种思路,但是数据不共享很局限,不能满足很多需求,比如多线程需要共同维护一个用户哈希表。
所以就需要给数据加锁。
手动加(实际上简陋、不可用的烂)锁:
结果打印:
这个程序证明了C语言的语句非原子操作,是可以分解成更多更细的指令的,不是一个真正意义上的原语,所以程序连死锁的机会都没有,各种乱跑。
PS:在所谓的lock操作i = 1;后加一个i等于0的判断,就可以知道i也是发生变化的。
最终方法
手动设的普通变量解决不了安全性问题,怎么办?其实,pthread库里是自带方法了。应该是线程锁吧,有指定的变量pthread_mutex_t和指定操作pthread_mutex_lock()、pthread_mutex_unlock()
运行结果
不再打印多余信息,说明两个线程互相不干扰,不再有安全性问题。
PS:一个线程锁只能互斥保护一个临界资源,这个临界资源其实也不是固定的,全看lock在哪用,其实是一个锁同时只能锁一处资源。
很多不互斥使用的资源,如果用同一个锁,肯定就尴尬了。这就好比你上锁,把自己的锁锁到别人自行车上了,别人拿不了车。这是不想要的结果。
所以要给不同的临界资源陪不同的锁——多初始化一个锁变量,用那个变量去加锁解锁就好了。
测试代码:
如果两个线程传同一个线程锁lock1,就会顺序执行:
验证测试程序如下,两个线程共同对一个(非全局)变量操作,根据初始打印可知地址一样,非全局。
#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!所以,只要区分好那把锁锁哪辆车就行了。
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- 使用 Syncthing 在多个设备间同步文件
- 交换机升级排障实例
- C#实现多线程的同步方法实例分析
- 浅谈chuck-lua中的多线程
- 科学知识:同步、异步、阻塞和非阻塞区别
- 同步文件备份工具 Super Flexible File Synchronizer Pro v4
- 探讨Ajax中同步与异步之间的区别
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- C#线程同步的三类情景分析
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析
- 分享我在工作中遇到的多线程下导致RCW无法释放的问题
- C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法