互斥锁和条件变量

Posted milaiko

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了互斥锁和条件变量相关的知识,希望对你有一定的参考价值。

同步概念

通信中的同步指协同步调,按预定的先后次序运行

线程同步, 指一个线程发出某一个功能调用时。在没有得到结果之前,该调用不返回,同时其他线程为保证数据一致性,不能调用该数据。

“同步”的目的,是为了避免数据混乱,解决与时间无关的错误。实际上,不仅线程间需要同步,进程间,信号间都需要同步机制。

数据混乱原因:

  1. 资源共享
  2. 调度随机
  3. 线程间缺少必要的同步机制

数据混乱代码
两个线程更改一个共享变量。可以发现两个线程之间并不正确地给共享变量counter递增。

#include "head.h"
#define NOOP 500
int couter;

void *doit(void *);

int main(int argc, char* argv[]){
    pthread_t tidA, tidB;

    pthread_create(&tidA, NULL, &doit, NULL);
    pthread_create(&tidB, NULL, &doit, NULL);

    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);

    exit(0);
}

void *doit(void *vptr){
    int i, val;

    for(i = 0; i<NOOP;i++){
        val = couter;
        printf("%lu:%d\\n", pthread_self(),val+1);
        couter = val+1;
    }
    return (NULL);
}

如何解决数据混乱问题,很简单的解决办法就是使用互斥锁mutex

互斥锁(互斥量)

#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t, *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);

还有以下函数

  • pthread_mutex_init
  • pthread_mutex_destory
  • pthread_mutex_lock
  • pthread_mutex_trylock //尝试lock,如果不行就不在那里阻塞
  • pthread_mutex_unlock

返回值: 成功为0,失败返回错误号

注意事项:
保证锁的粒度,越小越好

互斥锁:本质是结构体

加锁:pthread_mutex_lock() ,阻塞线程

解锁:pthread_mutex_unlock(), 唤醒阻塞在锁上的线程。

try锁:尝试加锁,成功加锁;失败直接返回错误号(EBUSY),不阻塞

如果试图上锁已被另外一个线程锁住的一个互斥锁,本线程会阻塞,直到该互斥锁解锁为止

静态初始化和动态初始化

  • 如果某个互斥锁变量是静态分配的,我们必须初始化为常值 PTHREAD_MUTEX_INITIALIZER
  • 如果在共享内存区中分配一个互斥锁,那么必须通过pthread_mutex_init 函数在运行时初始化。

初始化条件变量
pthread_mutex_t mutex;

  1. pthread_mutex_init(&mutex, NULL); 动态初始化
  2. pthread_mutex_t mutex = PTHREAD_COND_INITIALIZER; 静态初始化

使用锁的版本

//改正版本
#include "head.h"
#define NOOP 500
int couter;
pthread_mutex_t counter_mutex =PTHREAD_MUTEX_INITIALIZER;
void *doit(void *);

int main(int argc, char* argv[]){
    pthread_t tidA, tidB;

    pthread_create(&tidA, NULL, &doit, NULL);
    pthread_create(&tidB, NULL, &doit, NULL);

    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);

    exit(0);
}

void *doit(void *vptr){
    int i, val;

    for(i = 0; i<NOOP;i++){
    	pthread_mutex_lock(&counter_mutex);
        val = couter;
        printf("%lu:%d\\n", pthread_self(),val+1);
        couter = val+1;
        pthread_mutex_unlock(&counter_mutex);
    }
    return (NULL);
}

读写锁性质

特征:

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

读写锁又叫作共享锁-独占锁。当读写锁以读模式锁住时, 它是以共享模式锁住的;当它是以写模式锁住时,它是以独占模式锁住的。写独占,读共享

读写锁非常适合对数据结构读多于写的

死锁

是使用锁不恰当的信息

  • 对一个锁不断lock(线程试图对一个互斥量A加锁两次)
  • 两个线程执有一把锁请求另一把锁

条件变量

互斥锁适合于防止同时访问某个共享变量,但是我们需要另外某种在等待某个条件发生期间能让我们进入睡眠的东西。

本身不是锁,但是通常结合锁来使用。mutex

pthread_cond_t cond;
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict &mutex);

函数作用:
1、 阻塞等待条件变量cond满足
2、 释放已掌握的互斥锁相当于pthread_mutex_unlock(&mutex)
1、2两步为一个原子操作(也就是不可分的)
3、当被唤醒,pthread_cond_wait函数返回时, 解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

等待方式

  • 条件等待 pthread_cond_wait()
  • 计时等待 pthread_cond_timewait(),如果给定时间内没满足返回ETIMEOUT

使用的注意事项

  • mutex互斥锁必须是普通锁和适应锁
  • 调用pthread_cond_wait()之前,必须由本线程加锁

激活方式有两种

  • pthread_cond_wait()激活一个等待该条件的线程,存在多个等待线程按入队顺序激活其中一个
  • pthread_cond_broadcast激活所有等待线程

信号量

相当于初始化值为N的互斥量, N值,表示可以访问共享数据区的线程数

sem_init
sem_destory
sem_wait 对应着互斥锁的加锁
sem_post 对应着互斥锁的解锁

sem_wait: 1,如果信号量大于0,则信号量–
2, 信号量等于0,造成线程阻塞

sem_post 1, 将信号量++, 同时唤醒阻塞在信号量上的线程

信号量的初值,决定了占用信号量的线程个数

可以应用与线程和进程之中。

sem_init函数

SYNOPSIS
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
//对于pshared

0: 线程间同步
1:进程间同步

//对于value
N值,指定同时访问的线程数
 

以上是关于互斥锁和条件变量的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中使用互斥锁和条件变量实现带有信号的监视器

使用互斥锁和条件变量而不是信号量在 c++14 中打印从 1 到 10 的数字?

UNIX网络编程:互斥锁和条件变量

提升进程间共享互斥锁和提升共享互斥锁的进程间条件变量

Linux下的互斥锁和条件变量

使用 pthread、互斥锁和条件变量解决哲学家就餐问题