线程互斥

竞态条件

同一个进程的线程共享进程内的绝大部分资源,当一段访问这些共享资源的代码块,有可能被多个线程执行时,那么这段代码块就称为临界区。 当有多个线程并发的在临界区执行时,程序的执行结果会出现不确定性,这种情况称之为竞态条件。

实例:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<errno.h>

#define handle_error_en(en, msg) 
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while(0)

//glob为多线程共享资源
static int glob = 0;

//子线程访问修改共享资源glob
static void *thread_routine(void *arg)
{
    int loc, j;
    for (j = 0; j < 10000000; j++)
    {
        loc = glob;
        loc++;
        glob = loc;
    }
    return NULL;
}

int main()
{
    pthread_t t1,t2;
    int s;

    //创建两个线程并发访问修改共享资源glob
    s = pthread_create(&t1, NULL, thread_routine, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_create");
    }

    s = pthread_create(&t2, NULL, thread_routine, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_create");
    }

    s = pthread_join(t1, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_join");
    }
    s = pthread_join(t2, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_join");
    }

    printf("glob = %d
", glob);
    exit(EXIT_SUCCESS);
}

运行结果:

上述代码,每个线程都对glob进行了10000000次加1操作,glob的初值为0,因此理论上程序的执行结果应该是20000000。但是实际结果却没有达到预期。

出现以上结果的原因是因为多个线程并发的访问该临界区,从而出现了竞态条件。两个线程有可能以以下的时序执行,从而导致glob的最终值小于20000000。

互斥锁


多线程编程中,避免出现竞态条件的一项重要解决方案就是,保证多个线程在临界区是互斥的。所谓的互斥,就是指不能同时有多于一个线程进入临界区。 保证临界区互斥的重要技术,就是互斥锁。 互斥锁的初始化,有两种方式:静态初始化和动态初始化。

// 静态初始化一个全局的互斥锁
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
#include <pthread.h>
// 动态分配一个互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 释放动态分配的互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <pthread.h>
// 持有互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 释放互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

使用互斥锁,保证临界区互斥的一般思路是:

    1. 为该临界区分配一把互斥锁;
    2. 任何想要进入临界区的线程都必须先持有该互斥锁;
    3. 持有互斥锁运行于临界区的线程在离开临界区后必须释放该互斥锁;
    4. 假设某一临界区正在被一个线程A执行着,这意味着线程A持有该临界区的互斥锁M,如果此时有另一个线程B企图持有互斥锁M进入临界区,那么线程B将会进入阻塞状态。

使用互斥锁最常见的错误就是死锁,而所谓的死锁是指一个线程为了持有一把互斥锁而永远的阻塞了。 造成死锁原因主要有以下两种:

一个线程试图对其已经持有的互斥锁进行再次加锁;

当有需要持有多把锁时,线程间加锁的顺序不同时,也会造成死锁,如下图所示:

使用互斥锁修正glob的计算问题源代码如下:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<errno.h>

#define handle_error_en(en, msg) 
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while(0)

//glob为多线程共享资源
static int glob = 0;

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

//子线程访问修改共享资源glob
static void *thread_routine(void *arg)
{
    int loc, j;
    for (j = 0; j < 10000000; j++)
    {
        pthread_mutex_lock(&mtx);
        loc = glob;
        loc++;
        glob = loc;
        pthread_mutex_unlock(&mtx);
    }
    return NULL;
}

int main()
{
    pthread_t t1,t2;
    int s;

    //创建两个线程并发访问修改共享资源glob
    s = pthread_create(&t1, NULL, thread_routine, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_create");
    }

    s = pthread_create(&t2, NULL, thread_routine, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_create");
    }

    s = pthread_join(t1, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_join");
    }
    s = pthread_join(t2, NULL);
    if (0 != s)
    {
        handle_error_en(s, "pthread_join");
    }

    printf("glob = %d
", glob);
    exit(EXIT_SUCCESS);
}

运行结果:

原文地址:https://www.cnblogs.com/wanghao-boke/p/11976437.html