Linux线程同步-----互斥量(Mutex)

来源:互联网 发布:黑客编程新手教学 编辑:程序博客网 时间:2024/06/10 03:02
互斥量
与信号处理函数一样,线程在访问全局资源时也会遇到非原子操作导致的冲突(可重入问题). 比如两个线程要对同一个寄存器加1, 并行访问时可能会导致只加了一次.

不可重入操作的特点时,输出不仅依赖于输入,还依赖于状态, 比如加1 依赖于状态,这个状态是寄存器原值. 访问状态和修改状态不是原子操作的话,就会导致并发冲突。

生成锁
Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:
#include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_init(pthread_mutex_t *restrict mutex,  const pthread_mutexattr_t *restrict attr);pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
成功返回0, 失败返回错误号
pthread_mutex_init 对Mutex 做初始化,参数attr 用于设定Mutex 属性。如果用PTHREAD_MUTEX_INITIALIZER 初始化全局或者静态Mutex, 那它相当于用pthread_mutex_init 初始化时attr 为NULL。

加锁与释锁
#include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);//成功返回0,失败返回错误号
一个线程调用pthread_mutex_lock 时,如果锁被其它线程占用,它将被挂起等待,直到另外一个线程调用pthread_mutex_unlock 释放Mutex, 当前线程被唤醒后试图重新加锁。只有加锁成功,当前线程才能继续执行。
如果不想线程加锁时被挂起等待,可以用pthread_mutex_trylock。

Mutex 锁的原理
假设Mutex 锁为1 时表示锁空闲,为0 表示锁占用。那么lock / unlock 的实现伪码如下:
lock:    if(mutex > 0){            mutex = 0;            return 0;    }else{            //suspend;            goto lock;    }unlock:        mutex = 1;       唤醒其它因等待Mutex 而挂起的线程;    return 0;
可以看到,unlock 只有一个操作,可汇编码为单条指令,它是原子性的(单个cpu 时钟内,就能执行一条完成的汇编命令) 但是lock,包含两个操作:一个是对mutex 的状态判断,一个是对mutex 状态设置。这两个操作, 有因果关系,必须合并为一个原子操作才能避免多个线程同时加锁成功。 其实,大多数汇编指令都提供了swap 或者 exchange 指令,这个指令就可以实现将:状态判断和状态设置 合并为一个原子操作。以x86 的xchg 实现lock 和 unlock:
lock:    movb $0, %al;    xchgb %a1, mutex;//如果mutex 为0(被占用状态)那么交换后al 得到的是占用状态,原锁mutex 值不变(继续被占用)。    if(al > 0){        return 0;    }else{        //suspend;        goto lock;    }unlock:    movb $1, mutex;    唤醒其它因等待Mutex 而挂起的线程;    return 0;

Dead Lock 死锁
死锁有以下典型的情况: 1. 带锁线程的自己调用自己,如果线程自己调用自己就会因等待自己的锁释放而无限等待。这和递归不一样,递归是串行调用自己,根本不要锁机制。 2. 两个带锁线程AB 间的相互调用:如果线程A 获得锁lock1, 线程B 获得锁lock2. 此时线程A想获得lock2, 需要等待线程B 释放lock2, 但是线程B 又想获得锁lock1 B又等待A. 相当于AB 相互调用,相互等待对方释放锁。其实也相当于A 通过B 间接调用自己。经典的死锁问题: 5个哲学家就餐问题,就属于此类范畴(每位哲学都要等待右边的叉子释放)
有几个方法避免发生死锁: 1. 方法一是按顺序加lock1, lock2,lock3 加锁lock3 之前,必须先加lock2, 加lock2 前,必须先加lock1. 释放lock 时必须按倒序来 2. 如果确定锁的顺序比较困难,则尽量用pthread_mutex_trylock 代替 pthread_mutex_lock 以避免死锁。 3. 用串行代替并行。

Mutex小结
如果一个线程试图获取一个 mutex,但是没有成功,因为 mutex 已经被占用, 它将进入睡眠,让其他进程运行,直到 mutex 被其他进程释放.,两者都是开销比较大的操作,也就是 context switch 的开销.如果锁只是被其他线程占用非常短的时间,那么时间花在使的线程睡眠并唤醒它可能超过它使用 spinlock 持续获取锁的时间.

Mutex使用示例
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <sys/types.h>int seq = 0;pthread_mutex_t seq_lock = PTHREAD_MUTEX_INITIALIZER;int seq1 = 0;void *thr_func_without_lock(void *arg){    printf("The sequence(without lock) is %d ...\r\n", ++seq1);}void *thr_func_with_lock(void *arg){    pthread_mutex_lock(&seq_lock);    printf("The sequence is %d ...\r\n", ++seq);    pthread_mutex_unlock(&seq_lock);    return ((void *)0);}void demo(){    int i= 0;     for (i = 0 ; i < 10000; ++i)    {        pthread_t tid1, tid2, tid3;        pthread_create(&tid1, NULL, thr_func_without_lock, NULL);        pthread_create(&tid2, NULL, thr_func_without_lock, NULL);        pthread_create(&tid3, NULL, thr_func_without_lock, NULL);        pthread_join(tid1, NULL);        pthread_join(tid2, NULL);        pthread_join(tid3, NULL);    }}void lock_demo(){    int i = 0;    for (i = 0 ; i < 10000; ++i)    {        pthread_t tid1, tid2, tid3;        pthread_create(&tid1, NULL, thr_func_with_lock, NULL);        pthread_create(&tid2, NULL, thr_func_with_lock, NULL);        pthread_create(&tid3, NULL, thr_func_with_lock, NULL);        pthread_join(tid1, NULL);        pthread_join(tid2, NULL);        pthread_join(tid3, NULL);    }}int main(int argc, char **argv){    demo();    printf("\r\n");    lock_demo();    return 0;}




1 0