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

13 篇文章 0 订阅


多线程共享变量会涉及到数据的安全问题。

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


#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!
所以,只要 区分好那把锁锁哪辆车就行了。













  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值