多线程临界区问题及解决方法

临界区定义:函数内同时运行多个线程时引起的,由多条语句构成的代码块。
当多个线程对同一块内存空间进行操作时,就有可能产生临界区问题。

下面先看一个例子:

#include 
#include 
#include 
#include 
#define NUM_THREAD 100

void *thread_inc(void *arg);
void *thread_des(void *arg);
long long num = 0; //64 bit

int main(int argc, const char * argv[]) {

    pthread_t thread_id[NUM_THREAD];
    int i;

    printf("sizeof long long: %ld \n",sizeof(long long));
    for (i = 0; i < NUM_THREAD; i++) {

        if (i%2) {

            pthread_create(&thread_id[i], NULL, thread_inc, NULL);

        }else{

            pthread_create(&thread_id[i], NULL, thread_des, NULL);
        }
    }

    for (i = 0; i < NUM_THREAD; i++) {
        pthread_join(thread_id[i], NULL);
    }

    printf("result: %lld \n",num);

    return 0;
}

void *thread_inc(void *arg){
    int i;
    for (i = 0; i < 50000000; i++) {
        num += 1; //临界区
    }
    return NULL;
}
void *thread_des(void *arg){
    int i;
    for (i = 0; i < 50000000; i++) {
        num -= 1; //临界区
    }
    return NULL;
}

以上代码运行,理论上运行的结果为0,可是因为存在临界区问题,每次运行的结果都不一样。

引起问题的3种情况:
(1)2个线程同时执行thread_inc函数。
(2)2个线程同时执行thread_des函数。
(3)2个线程分别执行thread_inc函数和thread_des函数。

关于(3)的结论:如果2条语句访问同一内存空间,那么这2条不同语句由不同线程同时执行时,也有可能构成临界区。


对于多个线程对临界区进行访问,可以用线程同步来解决。

实现线程同步的两种方式:互斥量和信号量。

互斥量(加锁):

实现的语句:

pthread_mutex_t mutex;              //定义互斥量
pthread_mutex_init(&mutex, NULL);   //初始化互斥量,NULL表示不需要设置互斥量(锁)的属性
pthread_mutex_destroy(&mutex);      //销毁互斥量

pthread_mutex_lock(&mutex);         //进入临界区时加锁
pthread_mutex_unlock(&mutex);       //离开临界区时释放锁

进入临界区前调用 pthread_mutex_lock 函数,当调用该函数时,发现有其他线程已经进入临界区,则 pthread_mutex_lock 不会返回,直到里面的线程调用 pthread_mutex_unlock 函数退出临界区为止。也就是说其他线程让出临界区之前,当前线程将一直处于阻塞状态。

代码编写方法:

pthread_mutex_lock(&mutex);         //进入临界区时加锁
//临界区开始
......
//临界区结束
pthread_mutex_unlock(&mutex);       //离开临界区时释放锁

使用互斥量解决多线程访问临界区问题:

#include 
#include 
#include 
#include 
#define NUM_THREAD 100

void *thread_inc(void *arg);
void *thread_des(void *arg);
long long num = 0; //64 bit

pthread_mutex_t mutex;

int main(int argc, const char * argv[]) {

    pthread_t thread_id[NUM_THREAD];
    int i;

    pthread_mutex_init(&mutex, NULL); //创建互斥量

    printf("sizeof long long: %ld \n",sizeof(long long));
    for (i = 0; i < NUM_THREAD; i++) {

        if (i%2) {

            pthread_create(&thread_id[i], NULL, thread_inc, NULL);

        }else{

            pthread_create(&thread_id[i], NULL, thread_des, NULL);
        }
    }

    for (i = 0; i < NUM_THREAD; i++) {
        pthread_join(thread_id[i], NULL);
    }

    printf("result: %lld \n",num);
    pthread_mutex_destroy(&mutex); //销毁互斥量

    return 0;
}

void *thread_inc(void *arg){

    int i;
    pthread_mutex_lock(&mutex); //进入临界区时加锁
    for (i = 0; i < 50000000; i++) {
        num += 1; //临界区
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}
void *thread_des(void *arg){

    int i;
    pthread_mutex_lock(&mutex);
    for (i = 0; i < 50000000; i++) {
        num -= 1; //临界区
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}

使用互斥量后执行的结果正常,为0。


信号量:
注意:目前该方法已经被废弃了,这里也顺便罗列出来,感兴趣的可以研究学习一下。

实现的语句:

sem_t sem_one;  //定义信号量
sem_init(&sem_one, 0, 0);//第二个参数0表示只允许1个进程内部使用的信号量
                         //第三个参数0表示设置信号量的初始值为0

sem_destroy(&sem_one);  //销毁信号量


sem_wait(&sem_one);     //使信号量减1
sem_post(&sem_one);     //使信号量增1

原理:
调用 sem_init 函数时,操作系统会初始化信号量对象并记录下信号量的值。该值在调用 sem_post 函数时增1,调用 sem_wait 函数时减1。但是该信号量的值不能小于0,当信号量为0时,调用sem_wait 函数的线程会进入阻塞状态,直到有其他线程对该信号量调用 sem_post 函数,将信号量的值变为1,这样原本阻塞的线程就可以将该信号量重新减为0并跳出阻塞状态。

代码编写方法:

sem_wait(&sem_one);     //调用前,信号量应变为0
//临界区的开始
......
//临界区的结束
sem_post(&sem_one);     //使信号量增1

实例:

#include 
#include 
#include 

void *readstr(void *arg);
void *accu(void *arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;


int main(int argc, const char * argv[]) {

    pthread_t id_t1,id_t2;
    sem_init(&sem_one, 0, 0);//第二个参数0表示只允许1个进程内部使用的信号量
                             //第三个参数0表示设置信号量的初始值为0
    sem_init(&sem_two, 0, 1);

    pthread_create(&id_t1, NULL, readstr, NULL);
    pthread_create(&id_t2, NULL, accu, NULL);

    pthread_join(id_t1, NULL);
    pthread_join(id_t2, NULL);

    sem_destroy(&sem_one);
    sem_destroy(&sem_two);

    return 0;
}

void *readstr(void *arg){

    int i;
    for (i = 0; i < 5; i++) {
        fputs("Input num:", stdout);

        sem_wait(&sem_two);
        scanf("%d",&num);
        sem_post(&sem_one);
    }

    return NULL;
}

void *accu(void *arg){

    int sum = 0,i;
    for (i = 0; i < 5; i++) {
        sem_wait(&sem_one);
        sum += num;
        sem_post(&sem_two);
    }

    printf("Result: %d\n",sum);
    return NULL;
}

以上代码实现累加输入的5个数,线程1输入,线程2做累加。线程1每输入一个数,线程2就累加一次,在线程1没有输入时,线程2阻塞等待。

你可能感兴趣的:(Linux程序设计)