c++项目中 锁的使用

有一定的操作系统基础,互斥,信号量都有一定了解。 了解一下在项目中用到的锁。

1.首先是是初始化和去初始化,这两个函数都是库文件,可以直接调用,他们包含在头文件<pthread.h>中

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

初始化和去初始化我们一般写在构造函数和析构函数里面,如果初始化返回的不是0,说明初始化失败,抛出异常
c++项目中 锁的使用
除了使用 pthread_mutex_init 函数对 mutex 进行初始化,还可以使用特定的宏在声明 mutex 的时候直接赋值进行静态初始化。

ps:重复初始化一个已经初始化过的锁会导致未知行为。
如果尝试销毁一个锁定状态的mutex会导致未知行为。

2.加锁与解锁也是库函数,直接调用

// 普通加锁,重复加锁会阻塞进程
int pthread_mutex_lock (pthread_mutex_t *__mutex);
// 重复加锁不阻塞进程
int pthread_mutex_trylock (pthread_mutex_t *__mutex);
// 带有超时功能加锁
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abs_timeout);
// 解锁
int pthread_mutex_unlock (pthread_mutex_t *__mutex);

根据函数原型,函数的返回值都是整数,如果返回结果为0表示加锁成功,返回相应的bool值。
c++项目中 锁的使用
3.定义一个线程
pthread_mutex_t m_mutex;

4.创建线程

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

thread 指向线程标识符指针。
attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

5.终止线程
#include <pthread.h>
pthread_exit (status)

pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

6.关于上述应用实例

#include <pthread.h>
#include <stdio.h>

int gValue=0;
pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;

void *add(void*){
    pthread_mutex_lock(&gMutex);    // 加锁
    for (int i = 0; i < 10; ++i) {
        printf("[1]%d ", ++gValue);
    }
    pthread_mutex_unlock(&gMutex);  // 解锁
}

void *sub(void*){
    pthread_mutex_lock(&gMutex);  // 加锁
    for (int i = 0; i < 10; ++i) {
        printf("[2]%d ", --gValue);
    }
    pthread_mutex_unlock(&gMutex);  // 解锁
}


int main() {
    pthread_t p1, p2;

    pthread_create(&p1, NULL, add, NULL);
    pthread_create(&p2, NULL, sub, NULL);

    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    return 0;
}

关于条件变量
1.定义
pthread_cond_t m_cond;

2.初始化和去初始化,同样写在构造函数和析构函数中
返回值:函数成功返回0;任何其他返回值都表示错误

int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);

参数是线程和属性:初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。这个函数返回时,条件变量被存放在参数cv指向的内存中。

cond()
    {
        if (pthread_cond_init(&m_cond, NULL) != 0)
        {
            //pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }
    ~cond()
    {
        pthread_cond_destroy(&m_cond);
    }

释放条件变量。

注意:条件变量占用的空间并未被释放。

2.阻塞在条件变量上pthread_cond_wait

函数将解锁m_mutex参数指向的互斥锁,并使当前线程阻塞在m_cond参数指向的条件变量上。

  pthread_cond_wait(&m_cond, m_mutex);

pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

pthread_mutex_lock();
while (condition_is_false)
 pthread_cond_wait();
pthread_mutex_unlock();

阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

3.阻塞直到指定时间pthread_cond_timedwait
#include <pthread.h>
#include <time.h>

 bool timewait(pthread_mutex_t *m_mutex, struct timespec t)
    {
        int ret = 0;
        //pthread_mutex_lock(&m_mutex);
        ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);
        //pthread_mutex_unlock(&m_mutex);
        return ret == 0;
    }

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数t指定

pthread_cond_timedwait函数也是退出点。

4.解除在条件变量上的阻塞pthread_cond_signal

 bool signal()
    {
        return pthread_cond_signal(&m_cond) == 0;
    }

函数被用来释放被阻塞在指定条件变量上的一个线程。

必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

5.释放阻塞的所有线程pthread_cond_broadcast

bool broadcast()
    {
        return pthread_cond_broadcast(&m_cond) == 0;
    }

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程。


上一篇:第四章学习笔记


下一篇:redis6.0.5之BIO阅读笔记-后台IO操作