线程同步之互斥量

线程同步

首先,我们要明确互斥量和条件变量是线程用来同步彼此行为的两个工具。
众所周知,线程的主要优势在于可以通过全局变量来共享信息,但是在共享信息的时候必须满足以下两个条件:

  • 必须确保多个线程不会同时修改同一个变量
  • 某一个线程不会读取正由其他线程修改的变量。

保护对共享变量的访问:互斥量

术语临界区是指::访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是,同时访问同一共享资源的其他线程不应该中断该片段的执行。
可以使用互斥量来保证对任意共享资源的原子访问,是保护共享变量最常见的方法
互斥量的两种状态:

  • 已锁定
  • 未锁定
    注意:任何时候最多只有一个线程可以锁定该互斥量。一旦某一线程锁定该互斥量,将成为该互斥量的所有者,只有所有者才能给互斥量解锁。
    一般情况下,对每一共享资源会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:
  • 针对共享资源锁定互斥量
  • 访问共享资源
  • 对互斥量解锁

互斥量的静态分配和动态分配

互斥量的静态分配

互斥量属于pthread_mutex_t类型的变量,在使用之前必须对其初始化。
pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER

动态初始化互斥量

静态初始值PTHREAD_MUTEX_INITIALIZER只能对经由静态分配且携带默认属性的互斥量进行初始化,其他情况下,必须调用pthread_mutex_init()对互斥量进行初始化。

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

返回零成功。
参数mutex指定函数执行初始化操作的目标互斥量,参数attr是指向pthread_mutexattr_t类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量属性,设为NULL,则取默认属性。
在什么情况下必须使用函数pthread_mutex_init()函数来动态分配互斥量,而非静态初始化互斥量呢?

  • 动态分配于堆中的互斥量。
  • 互斥量是在栈中分配的自动变量。
  • 初始化经由静态分配,且不使用默认属性的互斥量。
互斥量的销毁

当不再需要经由自动或动态分配的互斥量时,应使用下面的函数将其销毁(使用默认属性静态初始化的互斥量不需要调用之)。

#include<pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);

返回0成功。
销毁安全的前提是:
1,互斥量处于未锁定的状态
2,后续无任何线程企图锁定它
经由其销毁的互斥量,可调用pthread_mutex_init()对其重新初始化。

加锁和解锁互斥量

#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
//both return 0 on success.

在下面我将贴出两个代码来说明互斥量的作用,为什么说互斥量可以保证对任意共享资源的原子访问,原子访问归根接地有是什么意思?
首先,我们来看这一段代码,两个线程同时执行一个临界区:

#include <stdio.h>
#include <pthread.h>
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadfunc(void *arg)
{
        int loops = *((int *) arg);
        int j;
        for (j = 0; j < loops; j++){
                glob++;
        }
        return NULL;
}
int main()
{
        pthread_t t1,t2;
        int loops = 10000;
        pthread_create(&t1, NULL, threadfunc, &loops);
        pthread_create(&t2, NULL ,threadfunc, &loops);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        printf("glob = %d\n", glob);
        return 0;
}

我们预期的值应该是2000,但事实上并不是,它是一个大于10000小于20000的随机值。
那么如果我对临界区加锁以后呢?

#include <stdio.h>
#include <pthread.h>
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadfunc(void *arg)
{
        int loops = *((int *) arg);
        int j;
        pthread_mutex_lock(&mtx);
        for (j = 0; j < loops; j++){
                glob++;
        }
        pthread_mutex_unlock(&mtx);
        return NULL;
}
int main()
{
        pthread_t t1,t2;
        int loops = 10000;
        pthread_create(&t1, NULL, threadfunc, &loops);
        pthread_create(&t2, NULL ,threadfunc, &loops);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        printf("glob = %d\n", glob);
        return 0;
}

这个时候我们保证了对临界区的原子访问,这样我们就总是能够得到我们的预期值了。

互斥量类型

在这了浅谈几个互斥量的类型,需要的时候请我自己在自行查阅资料吧。
PTHREAD_MUTEX_NORMAL:
该类型的互斥量不具有死锁检测功能(自检)。
PTHREAD_MUTEX_ERRORCHECK:
对此类互斥量的所有操作都会执行错误检查。
PTHREAD_MUTEX_RECURSIVE:
递归互斥量维护有一个锁计数器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值