线程同步——互斥量

互斥锁原理

生产者消费者模型里,有两个线程,生产者进程和消费者进程。这两个线程最坏的情况下会同时操作临界资源会引起线程同步的问题。
在这里插入图片描述
互斥量如何解决这一问题?
如上图,当线程1在操作临界资源的时候,它可以阻止另外一个线程访问临界资源。

在生产者与消费者模型中,引发线程同步的根本原因在于两个线程的指令可能是交叉执行的,每一个线程有3条指令,它可能先执行线程1的1条指令,在执行线程2的两条指令后接着执行线程1的指令,如此交叉往复。使用互斥量就会让两个线程的指令先后执行。依旧是生产者消费者模型,互斥量可能先让线程1执行完所有指令再执行线程2,也可能让线程2执行完所有指令再执行线程1。
这一效果也称为原子性,互斥量其实就是保证了关键指令的原子性。
原子性指的是一系列的操作不可被中断的特性,这一系列的操作要么全部执行完成,要么全部没有执行,不存在部分执行部分没有执行的情况。

互斥量是线程同步的最简单的方式,也把它成为互斥锁,它是处于两个状态之一的变量:要么是解锁状态,要么是加锁状态。这两个状态可以保证资源访问的串行。如果临界资源被某一线程所使用,另一个线程需要使用资源的话,只能等待正在使用资源的线程释放掉,解锁之后才能使用资源。

操作系统提供了互斥量API,可以直接使用API完成资源的加锁,解锁操作。pthread_mutex_t

模拟生产者消费者模型

#include <stdio.h>
#include <pthread.h>
#include <vector>

// 临界资源
int num = 0;

// 生产者
void *producer(void *) {
    int times = 1000000;
    while (times--)
        num += 1;

}

// 消费者
void *comsumer(void *) {
    int times = 1000000;
    while (times--)
        num -= 1;
}

int main() {

    for (int i = 0; i < 10; ++i) {
        pthread_t thread1, thread2;
        pthread_create(&thread1, NULL, &producer, NULL);
        pthread_create(&thread2, NULL, &comsumer, NULL);
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        printf("临界资源:%d\n", num);
    }

    return 0;
}

上述代码模拟生产者消费者模型,每次都对临界资源进行100万次操作,打印10次这样的结果如下。

临界资源:-950191
临界资源:-1632377
临界资源:-674487
临界资源:-413055
临界资源:-1384356
临界资源:-470107
临界资源:406732
临界资源:-590458
临界资源:-729773
临界资源:-353279

观察发现生产者消费者循环的次数都是一样的,此时的临界资源应该还是初始值0,但是结果却不是这样的。

为模型加锁

使用互斥锁改进代码,在生产者与消费者循环时进行加锁和解锁

#include <stdio.h>
#include <pthread.h>
#include <vector>

// 使用互斥量解决线程同步问题 初始化互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 临界资源
int num = 0;

// 生产者
void *producer(void *) {
    int times = 1000000;
    while (times--) {
        pthread_mutex_lock(&mutex);
        num += 1;
        pthread_mutex_unlock(&mutex);
    }
}

// 消费者
void *comsumer(void *) {
    int times = 1000000;
    while (times--) {
        pthread_mutex_lock(&mutex);
        num -= 1;
        pthread_mutex_unlock(&mutex);
    }
}

int main() {

    for (int i = 0; i < 10; ++i) {
        pthread_t thread1, thread2;
        pthread_create(&thread1, NULL, &producer, NULL);
        pthread_create(&thread2, NULL, &comsumer, NULL);
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        printf("临界资源:%d\n", num);
    }

    return 0;
}

输出结果如下:

临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0
临界资源:0

加锁之后每一次都带来了性能损耗,所以执行时间可能会变长。

总结互斥量
如果有多个线程同时访问临界资源,互斥量保证临界资源是被串行访问的。某个线程访问临界资源,线程首先给临界资源加锁,加锁好其他资源无法访问,其他线程只能等当前线程解锁后才能使用。就比如有一堆人上厕所,但厕所只有一个,一个人进去之后需要把门反锁上,这样他上厕所时候就不会有人开门,上完厕所后把门解锁,后面的人继续排队上厕所。

猜你喜欢

转载自blog.csdn.net/wankcn/article/details/108412418