当前位置:   article > 正文

【线程同步2】使用互斥锁实现线程同步

【线程同步2】使用互斥锁实现线程同步


互斥锁基本概念

互斥锁是一种使用频繁的同步手段,也被称为互斥量。对比信号量的使用,我们可以将互斥锁的使用理解为信号量初值仅为1的一种情况。

互斥锁是属于系统的内核级对象,它能够使线程拥有某个资源的绝对访问权,互斥锁主要包括使用数量、线程ID,递归计数器等,其中线程ID表示当前拥有互斥锁的线程,递归计数器表示线程拥有互斥锁的次数。

  • 当互斥锁的线程ID为0时,表示互斥锁不被任何线程所拥有,此时系统会发出该互斥锁的通知信号,等待该互斥锁的其他线程中的某一个线程会拥有该互斥锁,同时,互斥锁的线程ID为当前拥有该互斥锁的线程的线程ID。
  • 当互斥锁的线程ID不为0时,表示当前有线程拥有该互斥锁。系统不会发出互斥锁的通知信号。其他等待互斥锁的线程继续等待,直到拥有改互斥锁的线程释放互斥对象的拥有权。

互斥锁可以进行分类:

分类函数定义
静态分配互斥锁pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;
动态分配互斥锁pthread_mutex_init(&mutex, *mutexattr);pthread_mutex_destroy(&mutex);

每一个互斥锁都有一个数据类型为pthread_mutex_t的互斥变量,表格中的静态分配互斥锁中的PTHREAD_MUTEX_INITIALIZER是一个结构常量,所以是可以直接创建一个互斥锁用该常量进行初始化。

或者就使用动态的方式初始化一个互斥锁,这种方式的互斥锁在使用结束后一定要进行销毁。其中初始化的pthread_mutex_init的参数中需要传递一个mutexattr的属性值,而这种锁的属性共有四种。

属性值意义
PTHREAD_MUTEX_TIMED_NP这是缺省值,相当于NULL。当一个线程加锁后,其他请求这个锁的线程会进入一个队列等待,锁释放后按队列优先级获得锁。具有公平性。
PTHREAD_MUTEX_RECURSIVE_NP嵌套锁,允许同一个线程多次获得同一个锁,并多次解锁
PTHREAD_MUTEX_ERRORCHECK_NP检错锁,如果同一个线程请求同一个锁,返回EDEADLK;负责和PTHREAD_MUTEX_TIMED_NP操作一样(进入等待队列)
PTHREAD_MUTEX_ADAPTIVE_NP适应锁,如果被锁上了,等解锁后重新竞争,不存在等待队列,而是解锁后先到先得。

互斥锁相关函数

操作函数意义
阻塞加锁int pthread_mutex_lock(pthread_t *mutex)锁空闲,立即加锁否则阻塞等待直至解锁
非阻塞加锁int pthread_mutex_trylock(pthread_t *mutex)锁空闲,立即加锁;否则立即返回 EBUSY而非等待
避免死锁的加锁int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timesec *restrict tsptr);可设置等待时间,若超时锁未释放,返回ETIMEDOUT,避免两个或多个线程加了锁,又彼此等待锁的释放而产生死锁。
解锁int pthread_mutex_unlock(pthread_t *mutex)有加锁必然有解锁
销毁锁int pthread_mutex_destroy(pthread_mutex *mutex);互斥锁也是一种资源,使用完毕后需要释放

互斥锁的使用

静态互斥锁的使用

代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

using namespace std;

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    sem_t* p = (sem_t*)arg;
    for(int i=0; i<10; ++i){
        usleep(100);
        pthread_mutex_lock(&m);
        cout << pthread_self() << ":before" << endl;
        usleep(100);
        cout << pthread_self() <<  ":exit" << endl;
        pthread_mutex_unlock(&m);
    }
}

int main(){
    pthread_t tid;
    pthread_create(&tid, NULL, func, NULL);

    usleep(500); // 等待子进程跑起来

    func(NULL);
    pthread_join(tid, NULL);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

执行部分结果如下:

在这里插入图片描述

动态互斥锁的使用

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

using namespace std;


void* func(void* arg){
    pthread_mutex_t* p = (pthread_mutex_t*)arg;
    for(int i=0; i<10; ++i){
        usleep(100);
        pthread_mutex_lock(p);
        cout << pthread_self() << ":before" << endl;
        usleep(100);
        cout << pthread_self() <<  ":exit" << endl;
        pthread_mutex_unlock(p);
    }
}

int main(){
    pthread_mutex_t m;
    pthread_mutex_init(&m, NULL);
    pthread_t tid;
    pthread_create(&tid, NULL, func, &m);

    usleep(500); // 等待子进程跑起来

    func(&m);
    pthread_join(tid, NULL);
    pthread_mutex_destroy(&m);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

执行部分结果如下:

在这里插入图片描述
显然这两种方式的互斥锁都保证在临界区内只有一个线程运行,保证了线程的同步。

C++11标准库中的mutex

这里我们直接使用标准库初始化一个互斥锁,进行加锁和解锁,具体代码如下:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

int main(){
    mutex m;
    auto func = [&m](){  // 互斥锁是一种资源,不能拷贝,只能移动
        for(int i=0; i<5; i++){
            m.lock();  // 加锁
            cout << this_thread::get_id() << ":before" << endl;
            this_thread::sleep_for(500ms);
            cout << this_thread::get_id() << ":after" << endl;
            m.unlock(); // 解锁
        }
    };

    thread t(func);
    func();
    t.join();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

除此以外,我们可以不使用函数lock()unlock()函数,而是直接使用lock_guard来实现加锁和解锁的函数,具体代码如下:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

int main(){
    mutex m;
    auto func = [&m](){
        for(int i=0; i<5; i++){
            lock_guard<mutex> guard(m);
            cout << this_thread::get_id() << ":before" << endl;
            this_thread::sleep_for(500ms);
            cout << this_thread::get_id() << ":after" << endl;
        }
    };

    thread t(func);
    func();
    t.join();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

其中的lock_guard<mutex>是守护互斥锁,借助一个对象在离开其作用域时将会自发调用析构函数这个特点,以上的代码块当守护锁离开其所在的作用域时将会解锁。

生产者-消费者实现

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/336612
推荐阅读
相关标签
  

闽ICP备14008679号