1、概念

线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

2、练习

创建两个线程,让两个线程共享一个全局变量int g_var, 然后让每个线程数5000次数,看最后打印出这个 g_var 值是多少?

2.1 代码实现

理论上这个 g_var 的值应该是5000+5000==10000。

//线程同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

#define NUM 5000
int g_var = 0;

//线程1处理函数
void * mythread1(void *arg)
{   
    int i;
    int n;
    for ( i = 0; i < NUM; i++)
    {
        n = g_var;
        n++;
        g_var = n;
    }  
}
//线程2处理函数
void* mythread2(void *arg)
{   
    int i;
    int n;
    for ( i = 0; i < NUM; i++)
    {
        n = g_var;
        n++;
        g_var = n;
    }
}
int main()
{      
    int n = 99;

    pthread_t pthread1;
    int ret1 = pthread_create(&pthread1, NULL, mythread1, NULL);
    if(ret1 != 0)
    {
        printf("pthread create error, [%s]", strerror(ret1));
        return -1;
    }  

    pthread_t pthread2;
    int ret2 = pthread_create(&pthread2, NULL, mythread2, NULL);
    if(ret2 != 0)
    {
        printf("pthread create error, [%s]", strerror(ret2));
        return -1;
    }  

    sleep(2); 
    //回收子线程
    pthread_join(pthread1, NULL);
    pthread_join(pthread2, NULL);
    printf("g_var == %d\n",g_var);
    return 0;
}

2.2 结果和分析

经过多次测试最后的结果显示,有可能会出现g_var值少于5000*2=10000的情况。
原因:假如子线程A执行完了n++操作,还没有将n的值赋值给g_var失去了cpu的执行权,子线程B得到了cpu执行权,而子线程B最后执行完了g_var = n,而后失去了cpu的执行权;此时子线程A又重新得到cpu的执行权,并执行g_var = n操作(上次的中断点),这样会把线程B刚刚写回g_var的值被覆盖了,造成g_var值不符合预期的值。

2.3 数据混乱的原因

  • 资源共享(独享资源则不会)
  • 随机调度(线程操作共享资源的顺序不确定)
  • 线程间缺乏必要的同步机制

3、解决办法

Linux中提供一把互斥锁mutex(也称之为互斥量)。每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
线程1访问共享资源的时候要先判断锁是否锁着,如果锁着就阻塞等待;若锁是解开的就将这把锁加锁,此时可以访问共享资源,访问完成后释放锁,这样其他线程就有机会获得锁。

应该注意:图中同一时刻,只能有一个线程持有该锁,只要该线程未完成操作就不释放锁。

使用互斥锁之后,两个线程由并行操作变成了串行操作,效率降低了,但是数据不一致的问题得到解决了。

3.1 互斥锁

假如线程A和线程B同时访问共享资源,当线程A想要去访问共享资源的时候,要先获得锁,如果锁被占用,则加锁不成功,需要阻塞等待对方释放锁;若锁没有被占用,则锁获得成功——>加锁,然后操作共享资源,操作完成后,必须解锁;同理B和A也一样。

3.2 互斥锁使用步骤

  1. 创建一把互斥锁:pthread_mutex_t mutex; //mutex = 1;
  2. 在main函数中初始化互斥锁:pthread_mutex_init(&mutex, NULL);
  3. 锁的使用——>在使用共享资源前加锁:pthread_mutex_lock(&mutex);
  4. 锁的使用——>在操作完共享资源后解锁:pthread_mutex_unlock(&mutex);
  5. 线程执行完后在main函数中销毁互斥锁:pthread_mutex_destroy(&mutex);

3.3 解决代码实现

//线程同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

#define NUM 5000
int g_var = 0;

//创建一个互斥锁
pthread_mutex_t mute;

//线程1处理函数
void * mythread1(void *arg)
{   
    int i;
    int n;
    for ( i = 0; i < NUM; i++)
    {   
        //在访问共享资源前加锁
        pthread_mutex_lock(&mute);
        n = g_var;
        n++;
        g_var = n;
        //访问完共享资源后解锁
        pthread_mutex_unlock(&mute);
    }  
}
//线程2处理函数
void* mythread2(void *arg)
{   
    int i;
    int n;
    for ( i = 0; i < NUM; i++)
    {   
        pthread_mutex_lock(&mute);
        n = g_var;
        n++;
        g_var = n;
        pthread_mutex_unlock(&mute);
    }
}
int main()
{      

    //初始化互斥锁
    pthread_mutex_init(&mute, NULL);

    pthread_t pthread1;
    int ret1 = pthread_create(&pthread1, NULL, mythread1, NULL);
    if(ret1 != 0)
    {
        printf("pthread create error, [%s]", strerror(ret1));
        return -1;
    }  

    pthread_t pthread2;
    int ret2 = pthread_create(&pthread2, NULL, mythread2, NULL);
    if(ret2 != 0)
    {
        printf("pthread create error, [%s]", strerror(ret2));
        return -1;
    }  

    sleep(2); 
    //回收子线程
    pthread_join(pthread1, NULL);
    pthread_join(pthread2, NULL);
    printf("g_var == %d\n",g_var);

    //释放互斥锁
    pthread_mutex_destroy(&mute);
    return 0;
}