Linux线程同步之互斥锁和读写锁

Source

Linux线程同步之互斥锁和读写锁

一 互斥锁

Linux 中提供一把互斥锁 mutex(也称之为互斥量)。每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

1.1 互斥锁及相关函数使用

1.pthread_mutex_init 函数 
初始化一个互斥锁(互斥量),初值可看作 1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);1:传出参数,调用时应传 &mutex

restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过除本指针以外的其他变量或指针修改。

参2:互斥量属性,是一个传入参数,通常传 NULL,选用默认属性(线程间共享)1.静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;

2.动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL);
2.pthread_mutex_destroy 函数 
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.pthread_mutex_lock 函数 
加锁,可理解为将 mutex--(或 -1),操作后 mutex 的值为 0int pthread_mutex_lock(pthread_mutex_t *mutex);
4.pthread_mutex_unlock 函数 
解锁,可理解为将 mutex++(或 +1),操作后 mutex 的值为 1int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.pthread_mutex_trylock 函数 
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

注:以上 5 个函数的返回值都是成功返回 0, 失败返回错误号。

1.2 lock 与 unlock

lock 尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock 主动解锁,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级及调度,默认为先阻塞、先唤醒。

1.3 lock 与 trylock

lock 加锁失败会阻塞,等待锁释放。
trylock 加锁失败直接返回错误号(EBUSY),不阻塞。

1.4 互斥锁实现线程同步

/* 线程之间共享资源stdout */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *tfn(void *arg)
{
    
      
    srand(time(NULL));

    while (1) {
    
      
        printf("hello ");
        /* 模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误 */
        sleep(rand() % 3);	
        printf("world\n");
        sleep(rand() % 3);
    }

    return NULL;
}

int main(void)
{
    
      
    pthread_t tid;
    srand(time(NULL));

    pthread_create(&tid, NULL, tfn, NULL);
    while (1) {
    
      
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
    }
    return 0;
}

1.5 死锁

  1. 线程试图对同一个互斥量 A 加锁两次;
  2. 线程 1 拥有 A 锁,请求获得 B 锁,线程 2 拥有 B 锁,请求获得 A 锁,形成循环阻塞。

二 读写锁

2.1 读写锁的特性

  1. 读写锁是 “写模式加锁” 时, 解锁前,所有对该锁加锁的线程都会被阻塞。
  2. 读写锁是 “读模式加锁” 时, 如果线程以读模式对其加锁会成功,如果线程以写模式加锁会阻塞。
  3. 读写锁是 “读模式加锁” 时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求,优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高,优先获取读写锁。

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的。读写锁非常适合于对数据结构读的次数远大于写的情况。

注:1.锁只有一把 2.读共享 写独占 3.写锁优先级高。
例:T1、T4、T5为请求读锁,T2、T3为请求写锁,若此时T1持有读锁,T2、T4、T5同时请求,则T4、T5被阻塞,优先T2获取锁,之后T4、T5才会获取锁(顺序与优先级和调度有关),若在T4、T5获取锁之前T3出现,则继续优先写锁T3先获取,之后再T4、T5获取。

2.2 读写锁及相关函数使用

1.pthread_rwlock_init 函数 
初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);2:attr 读写锁属性,通常使用默认属性,传 NULL 即可。
2.pthread_rwlock_destroy 函数 
销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
3.pthread_rwlock_rdlock 函数 
以读方式请求读写锁。(常简称为:请求读锁)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
4.pthread_rwlock_wrlock 函数 
以写方式请求读写锁。(常简称为:请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
5.pthread_rwlock_unlock 函数 
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
6.pthread_rwlock_tryrdlock 函数 
非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
7.pthread_rwlock_trywrlock 函数 
非阻塞以写方式请求读写锁(非阻塞请求写锁)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

注:以上 7 个函数的返回值都是成功返回 0, 失败直接返回错误号。

8.pthread_rwlock_t 类型 用于定义一个读写锁变量。

2.3 读写锁实现线程同步

/* 3个线程不定时 "写" 全局资源,5个线程不定时 "读" 同一全局资源 */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

//全局资源
int counter;                          
pthread_rwlock_t rwlock;

void *th_write(void *arg)
{
    
      
    int t;
    int i = (int)arg;

    while (1) {
    
      
    	// 保存写之前的值
        t = counter;
        usleep(1000);

        pthread_rwlock_wrlock(&rwlock);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);
		// 给 r 锁提供机会
        usleep(9000);
    }
    return NULL;
}

void *th_read(void *arg)
{
    
      
    int i = (int)arg;

    while (1) {
    
      
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);
		// 给写锁提供机会
        usleep(2000);                
    }
    return NULL;
}

int main(void)
{
    
      
    int i;
    pthread_t tid[8];

    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);

    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);
	//释放读写琐
    pthread_rwlock_destroy(&rwlock);
    return 0;
}