linux 应用层同步和互斥机制之互斥量

1、互斥量(Mutex)

1.1初始化

互斥量是属于pthread_mutex_t类型变量,使用之前必须初始化。

初始化方法有两种:静态初始化和动态初始化

静态初始化:pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER

pthread_mutex_t mtx是个结构体类型,静态初始化时,只能在定义时,不能先定义再用PTHREAD_MUTEX_INITIALIZER初始化。

比如: static pthread_mutex_t mtx;

          Mtx = PTHREAD_MUTEX_INITIALIZER;

编译会报错。

#include

int

pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

                                                                                                                                            成功:0 失败:非0

动态初始化:pthread_mutex_init

restrict修饰符,用来修饰一个指针,通俗讲:只要这个指针活着,我保证这个指针独享这片内存,没有‘别人’可以修改这个指针指向的这片内存,所有修改都得通过这个指针来。

原则上,在如下情况,应使用动态初始化:

  1. 动态分配于堆中的互斥量
  2. 互斥量是在栈中分配的自动变量
  3. 静态分配,但不使用默认属性的互斥量
  4. 静态分配,但没有在定义的时候静态初始化的

所以,使用静态初始化的情况如下:静态分配的变量,使用默认属性。包括:全局变量,局部static静态变量。

如下的静态初始化

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

static struct vhost_user vhost_user = {

    .fdset = {

        .fd = { [0 ... MAX_FDS - 1] = {-1, NULL, NULL, NULL, 0} },

        .fd_mutex = PTHREAD_MUTEX_INITIALIZER,

        .num = 0

    },

    .vsocket_cnt = 0,

    .mutex = PTHREAD_MUTEX_INITIALIZER,

};

互斥量属性

通常情况下,使用默认属性即可。静态初始化使用的就是默认属性。

动态初始化函数pthread_mutex_init的第二个参数,为NULL时,就是使用默认属性。

截取DPDK源码中的一段

struct vhost_user_reconnect_list {

    struct vhost_user_reconnect_tailq_list head;

    pthread_mutex_t mutex;

};

static int

vhost_user_reconnect_init(void)

{

    int ret;

    pthread_mutex_init(&reconn_list.mutex, NULL);

    ...

    return ret;

}

1.2 获取和释放

获取互斥量:pthread_mutex_lock(pthread_mutex_t *mutex)

释放互斥量:pthread_mutex_unlock(pthread_mutex_t *mutex);

尝试获取互斥量:pthread_mutex_trylock(pthread_mutex_t *mutex);

#include

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);                                                                                                                                           成功:0 失败:非0   

要锁定互斥量,在调用pthread_mutex_lock时需要指定互斥量。如果互斥量当前处于未锁定状态,该调用将锁定互斥量并立即返回。如果其他线程已经锁定了这一互斥量,那么该调用会一直阻塞,直到该互斥量被解锁。

Pthread_mutex_trylock函数调用在获取互斥量时,如果互斥量当前处于未锁定状态,跟pthread_mutex_lock一样,锁定互斥量并立即返回。如果其他线程已经锁定这一互斥量,该调用不会阻塞,会返回EBUSY错误。

如果发起pthread_mutex_lock调用的线程自身之前已经将目标互斥量锁定,此时会产生什么结果呢?这跟互斥量属性有关。关于Mutex属性我们暂不做过多说明。后续遇到实际情况再补充。这里简单说明一下几个互斥量类型(类型属于属性的一种)。

PTHREAD_MUTEX_NORMAL

         该类型的互斥量不具备死锁自检功能。如线程试图对已经由自己锁定的互斥量加锁,会发生死锁。互斥量处于未锁定状态或者已经由其他线程锁定,对其解锁会导致不确定的结果。(Linux上会成功)

PTHREAD_MUTEX_ERRORCHECK

         对此类互斥量的所有操作都会执行错误检查。上面说的几种情况,都会导致函数返回错误。这类互斥量运行起来比一般类型慢,可以作为调试工具用。

PTHREAD_MUTEX_RECURSIVE

         递归互斥量属性。支持线程对已经由自己锁定的互斥量加锁。只要保证加锁的次数和解锁的次数匹配即可。解锁时如果互斥量处于未锁定状态,或者由其他线程锁定,操作会失败。

在Linux上,PTHREAD_MUTEX_DEFAULT类型互斥量的行为与PTHREAD_MUTEX_NORMAL相似。

如果由不止一个线程在等待获取由函数pthread_mutex_unlock解锁的互斥量,则无法判断究竟哪个线程将如愿以偿。

1.3 销毁

#include

int

pthread_mutex_destroy(pthread_mutex_t *restrict mutex);                                                                                                                                          成功:0 失败:非0

这个销毁函数是与动态初始化函数配套使用的。静态初始化的Mutex不需要销毁操作。

只有当互斥量处于未锁定状态,且后续也无任何线程企图锁定它时,将其销毁才是安全的。

若互斥量驻留在动态分配的一片内存区域中,应在free此内存区域之前将其销毁。对于自动分配的互斥量(一般指局部非静态变量),也应在宿主函数返回前将其销毁。

经由pthread_mutex_detroy销毁的互斥量,可以用pthread_mutex_init对其重新初始化。

1.4死锁

有人的地方就有江湖。有锁的地方,就有死锁的风险。

一种典型的死锁情况:

linux 应用层同步和互斥机制之互斥量_第1张图片

线程A和B都成功锁住了一个互斥量,接着试图对已经被另一个线程锁定的互斥量加锁。两个线程无限制的等待下去。

另一种死锁情况:忘记释放锁。

一般在有goto, return的地方,容易忽略unlock。

1.5适用场景

因为Mutex是有名字的,可以找得到。所以互斥量可以应用在统一进程的不同线程之间,也可以用在不同进程的线程之间,也可以用在进程之间。

如果跨进程使用,互斥量需要是进程间共享的。注意条件有:

  1. 需要放在共享内存中,但不能放在reserve预留内存中。
  2. 要调用pthread_mutex_setpshared接口来设置进程间共享属性。
  3. 当一个进程异常退出了,没有释放mutex。怎么办?可以通过设置属性,获取锁时,发现锁被持有并且锁的owner不存在了,就会返回特定错误值EOWNERDEAD。获取错误值后,一系列操作来处理错误值:
  1. 首先调用pthread_mutex_consistent_np函数来恢复该锁的一致性
  2. 然后调用pthread_mutex_unlock来解锁
  3. 接下来在调用加锁

所以,跨进程使用Mutex比较复杂。遇到实际案例再做补充。

你可能感兴趣的:(linux,linux,c语言,网络)