【Linux】-- 单例模式(线程安全版本)

目录

线程安全的单例模式

饿汉实现方式和懒汉实现方式

饿汉方式实现单例模式

懒汉方式实现单例模式

懒汉方式实现单例模式(线程安全版本)

普通版本的线程池

实现单例模式

线程池单例模式(线程安全版本)代码

STL、智能指针和线程安全

其他常见的各种锁

系统编程中的锁

自旋锁

读者写者问题

读写锁

伪代码

读写锁的加锁、解锁过程

读写锁接口


线程安全的单例模式

#问:什么是单例模式?

【C++】-- 特殊类设计_川入的博客-CSDN博客

单例模式相当于,一个类最终只能定义一个对象。单例模式常见的就是:

  • 饿汉模式。
  • 懒汉模式。

        因为,凡是需要被设置成单例的,往往就是因为其本身不需要被频繁加载到内存。可能在系统里只要有一份就行了 / 可能其本身占的空间太大(将软件运行起来,加载的时候,其会创建一堆的全局变量、全局对象。并且如果我们在代码当中用同一个类,定义了多个对象,如果对象都是一样,就极有可能出现重复。这样是不合理的)。所以,通过单例,实现特定的类只能实现一个对象。

饿汉实现方式和懒汉实现方式

[洗完的例子]
  • 吃完饭,立刻洗碗,这种就是饿汉方式。因为下一顿吃的时候可以立刻拿着碗就能吃饭。
  • 吃完饭,先把碗放下,然后下一顿饭用到这个碗了再洗碗,就是懒汉方式

        懒汉方式最核心的思想是 "延时加载",从而能够优化服务器的启动速度。

饿汉方式实现单例模式

template <typename T>
class Singleton {
    static T data;
public:
    static T* GetInstance() {
        return &data;
    }
};
        只要通过 Singleton 这个包装类来使用 T 对象 , 则一个进程中只有一个 T 对象的实例 .

懒汉方式实现单例模式

​template <typename T>
class Singleton {
    static T* inst;
public:
    static T* GetInstance() {
        if (inst == NULL)
            inst = new T();
        return inst;
    }
};

懒汉方式实现单例模式(线程安全版本)

#:简单线程池的实现?

【Linux】-- 线程池_川入的博客-CSDN博客

普通版本的线程池

log.hpp

#pragma once
#include <cstdarg>
#include <ctime>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1 // 正常
#define WARNING 2 // 警告 -- 没出错
#define ERROR   3 // 错误 -- 不影响后续执行(一个功能因为条件等,没有执行)
#define FATAL   4 // 致命 -- 代码无法继续向后执行

const char* gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL",
};

#define LOGFILE "./threafpool.log"

// 完整的日志功能,至少:日志等级 时间 日志内容 支持用户自定义
void logMessage(int level, const char* format, ...)// level:日志等级; format, ...:用户传参、日志对应的信息等。
{
#ifndef DEBUG_SHOW
    if(level == DEBUG) return;
#endif
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);

    //struct tm* localtime = localtime(&timestamp); //- 详细时间输出操作
    //localtime->tm_year;

    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // 这个时候就有一个可变参数列表的起始地址

    // 向屏幕中直接打印
    // vprintf(format, args);

    // 向缓冲区logBuffer中打印
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // 向屏幕
    printf("%s%s\n", stdBuffer, logBuffer);

    // 向文件
    FILE *fp = fopen(LOGFILE, "a");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

Task.hpp

#pragma once

#include "log.hpp"
#include <iostream>
#include <string>
#include <functional>

typedef std::function<int(int, int)> func_t;

class Task
{
public:
    Task(){}
    Task(int x, int y, func_t func):_x(x), _y(y), _func(func)
    {}

    void operator()(std::string& name)
    {
        //std::cout << "线程" << name << "处理完成,结果是" << _x << "+" << _y << "=" << _func(_x, _y) << std::endl;
        logMessage(WARNING, "%s处理完成: %d+%d=%d | %s | %d", 
        name.c_str(), _x, _y, _func(_x, _y), __FILE__, __LINE__); // __FILE__, __LINE__:预处理符。
    }
public:
    int _x;
    int _y;

    // int type;
    func_t _func;
};

thread.hpp

#pragma once
#include <string>
#include <pthread.h>
#include <cstdio>

// 对线程的封装 - 不是完全必要,但是这样便于后期的统一管理

typedef void*(*fun_t)(void*);

// 整合线程的数据
class ThreadData
{
public:
    std::string _name;
    void* _args;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void* args):_func(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);
        _name = nameBuffer;

        _tdata._args = args;
        _tdata._name = _name;
    }

    void start()
    {
        pthread_create(&_tid, nullptr, _func, (void*)&_tdata);
    }

    void join()
    {
        pthread_join(_tid, nullptr);
    }

    // 未来不再使用线程id了,因为其是一个地址不便于我们查看
    std::string name()
    {
        return _name;
    }
    
    ~Thread()
    {}

private:
   std::string _name;
   fun_t _func;
   ThreadData _tdata;
   pthread_t _tid;
};

lockGuard.hpp

#pragma once

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

// RAII风格的加锁方式
class lockGuard
{
public:
    lockGuard(pthread_mutex_t *mtx):mtx_(mtx)
    {
        pthread_mutex_lock(mtx_);

    }
    ~lockGuard()
    {
        pthread_mutex_unlock(mtx_);
    }

private:
    pthread_mutex_t *mtx_;
};

threadPool.hpp

#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include <iostream>

const int g_thread_num = 3;

template <class T>
class ThreadPool
{
public:
    // 返回锁的地址
    pthread_mutex_t *getMutex()
    {
        return &_lock;
    }

    bool isEmpty()
    {
        return _task_queue.empty();
    }

    void waitCond()
    {
        pthread_cond_wait(&_cond, &_lock);
    }

    T getTask()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

public:
    ThreadPool(int thread_num = g_thread_num) : _num(thread_num)
    {
        for (int i = 1; i <= _num; i++)
        {
            _threads.push_back(new Thread(i, routine, this));
        }
        pthread_mutex_init(&_lock, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    // 1.run - 将线程跑起来
    void run()
    {
        for (auto &iter : _threads)
        {
            iter->start();
            // std::cout << iter->name() << " 启动成功" << std::endl;
            logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");
        }
    }

    // 未来所有执行流所执行的方法 - 核心取任务、执行任务的逻辑
    static void *routine(void *args) // 因为在类当中,如果是一个成员方法,其会有一个隐藏的参数this指针,所以我们需要使用static进行修饰。
    {
        ThreadData *td = (ThreadData *)args;
        ThreadPool<T> *tp = (ThreadPool<T> *)td->_args;
        while (true)
        {
            T task; // 对应任务的对象
            {
                // lock
                lockGuard lockguard(tp->getMutex());

                // while(task_queue_.empty()) wait();
                while (tp->isEmpty())
                    tp->waitCond();

                // 获取任务 - 100%有任务
                task = tp->getTask(); // 任务队列是共享的->将任务从共享,拿到自己的私有空间

            } // 自动释放锁

            // 处理任务
            task(td->_name); // 要求每一个任务都要提供一个仿函数
        }
    }

    // 2.pushTask - 将任务放到任务池里
    void pushTask(const T &task)
    {
        lockGuard lockguard(&_lock); // 加锁
        _task_queue.push(task);      // 压入任务
        pthread_cond_signal(&_cond); // 唤醒线程
    }                                // 自动释放锁

    // void join()
    // {
    //     for (auto &iter : _threads)
    //     {
    //         iter->join();
    //     }
    // }

    ~ThreadPool()
    {
        for (auto &iter : _threads)
        {
            iter->join();
            delete iter;
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

private:
    std::vector<Thread *> _threads;
    int _num;
    std::queue<T> _task_queue;
    pthread_mutex_t _lock;
    pthread_cond_t _cond;
};

testMain.cc

#include "threadPool.hpp"
#include "Task.hpp"
#include "log.hpp"
#include <ctime>
#include <iostream>

int main()
{
    logMessage(NORMAL, "%s %d %c %f\n", "这是一条日志信息", 1234, 'a', 3.14);
    srand((unsigned long)time(nullptr) ^ getpid()); // 利用 ^ getpid()让数据更随机
    ThreadPool<Task> *tp = new ThreadPool<Task>();
    tp->run();

    while (true)
    {
        // 生产的过程(制作任务的时候,要花时间)
        int x = rand() % 1000;
        usleep(5000); // 模拟制作任务的时候,花费的时间
        int y = rand() % 1000;

        Task t(x, y, [](int x, int y)->int{
            return x + y;
        });

        // std::cout << "制作任务完成" << std::endl;
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);
        
        // 推送任务到线程池中
        tp->pushTask(t);

        sleep(1); // 防止刷新过快
    }
    
    return 0;
}

实现单例模式

增加成员:

// 通过定义一个静态的指针
static ThreadPool<T>* _thread_ptr;

初始化:

// 在类外来对静态成员进行初始化
template<class T>
ThreadPool<T>* ThreadPool<T>::_thread_ptr = nullptr;

        想要实现单例模式的线程池,首先必须在我们的类当中定义一个静态的线程池对象。之所以称之为单例,是因为其本身的构造、拷贝构造、赋值语句,基本上都是需要被去掉的。

        自行实现一个手动构造的自定义函数。

Note:

        单例只是将构造变为了私有的,必须要有构造,要不然就无法创建出单例。

于是对于调用的变化:

// 运行一个线程库
ThreadPool<Task>::getThreaedPool()->run();

// 推送任务到线程池中
ThreadPool<Task>::getThreaedPool()->pushTask(t);

        这个时候,对于C++语言上所学习的单例模式的使用,看起来是没有问题的,但是:

#问:如果单例本身也在被多线程申请使用呢?

        如果有多个线程去获取、使用这个线程池,我们就需要考虑多线程使用单例(调用getThreaedPool)的过程。

        就有可能出现很多线程都进入判断。

        就可能导致多个线程new出不同的对象,所以getThreaedPool函数在多线程下并不是线程安全的,所以我们需要加一把锁:

增加成员:

// 通过定义一个静态的互斥量
static pthread_mutex_t _mutex;

初始化:

// 在类外来对静态成员进行初始化
template <typename T>
pthread_mutex_t ThreadPool<T>::_mutex = PTHREAD_MUTEX_INITIALIZER;

        这个时候就可以了,但是并不够好,因为:未来任何一个线程想获取单例,都必须调用getThreadPool接口。但是对于单例模式的线程安全问题只有在第一次的时候,一旦单例被初始化了_thread_ptr一定不为空。于是在后面不免会出现,线程跑过来,先加锁,然后什么都不做,然后再解锁。这不是闲的没事干是什么?导致会存在大量的申请和释放锁的行为,这个是无用且浪费资源,所以需要再调整。

        通过添加if判断:可以有效减少未来必定要进行加锁检测的问题,拦截大量的在已经创建好单例的时候,剩余线程请求单例的而直接访问锁的行为。

线程池单例模式(线程安全版本)代码

log.hpp

#pragma once
#include <cstdarg>
#include <ctime>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1 // 正常
#define WARNING 2 // 警告 -- 没出错
#define ERROR   3 // 错误 -- 不影响后续执行(一个功能因为条件等,没有执行)
#define FATAL   4 // 致命 -- 代码无法继续向后执行

const char* gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL",
};

#define LOGFILE "./threafpool.log"

// 完整的日志功能,至少:日志等级 时间 日志内容 支持用户自定义
void logMessage(int level, const char* format, ...)// level:日志等级; format, ...:用户传参、日志对应的信息等。
{
#ifndef DEBUG_SHOW
    if(level == DEBUG) return;
#endif

    // va_list ap; // va_list本质上就是char类型的指针
    // va_start(ap, format); // 让指针指向栈帧对应的结构
    // int x = va_arg(ap, int); // 通过具体的类型,来从通过指针提取特定的值 —— 没有参数就返回NULL

    // va_end(ap); // 将指针设置为空(相当于:ap = nullptr)

    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);

    //struct tm* localtime = localtime(&timestamp); //- 详细时间输出操作
    //localtime->tm_year;

    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // 这个时候就有一个可变参数列表的起始地址

    // 向屏幕中直接打印
    // vprintf(format, args);

    // 向缓冲区logBuffer中打印
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // 向屏幕
    printf("%s%s\n", stdBuffer, logBuffer);

    // 向文件
    FILE *fp = fopen(LOGFILE, "a");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

lockGuard.hpp

#pragma once

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

// RAII风格的加锁方式
class lockGuard
{
public:
    lockGuard(pthread_mutex_t *mtx):mtx_(mtx)
    {
        pthread_mutex_lock(mtx_);

    }
    ~lockGuard()
    {
        pthread_mutex_unlock(mtx_);
    }

private:
    pthread_mutex_t *mtx_;
};

thread.hpp

#pragma once
#include <string>
#include <pthread.h>
#include <cstdio>

// 对线程的封装 - 不是完全必要,但是这样便于后期的统一管理

typedef void*(*fun_t)(void*);

// 整合线程的数据
class ThreadData
{
public:
    std::string _name;
    void* _args;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void* args):_func(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);
        _name = nameBuffer;

        _tdata._args = args;
        _tdata._name = _name;
    }

    void start()
    {
        pthread_create(&_tid, nullptr, _func, (void*)&_tdata);
    }

    void join()
    {
        pthread_join(_tid, nullptr);
    }

    // 未来不再使用线程id了,因为其是一个地址不便于我们查看
    std::string name()
    {
        return _name;
    }
    
    ~Thread()
    {}

private:
   std::string _name;
   fun_t _func;
   ThreadData _tdata;
   pthread_t _tid;
};

Task.hpp

#pragma once

#include "log.hpp"
#include <iostream>
#include <string>
#include <functional>

typedef std::function<int(int, int)> func_t;

class Task
{
public:
    Task(){}
    Task(int x, int y, func_t func):_x(x), _y(y), _func(func)
    {}

    void operator()(std::string& name)
    {
        //std::cout << "线程" << name << "处理完成,结果是" << _x << "+" << _y << "=" << _func(_x, _y) << std::endl;
        logMessage(WARNING, "%s处理完成: %d+%d=%d | %s | %d", 
        name.c_str(), _x, _y, _func(_x, _y), __FILE__, __LINE__); // __FILE__, __LINE__:预处理符。
    }
public:
    int _x;
    int _y;

    // int type;
    func_t _func;
};

threadPool.hpp

#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include <iostream>

const int g_thread_num = 3;

template <class T>
class ThreadPool
{
public:
    // 返回锁的地址
    pthread_mutex_t *getMutex()
    {
        return &_lock;
    }

    bool isEmpty()
    {
        return _task_queue.empty();
    }

    void waitCond()
    {
        pthread_cond_wait(&_cond, &_lock);
    }

    T getTask()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

private:
    ThreadPool(int thread_num = g_thread_num) : _num(thread_num)
    {
        for (int i = 1; i <= _num; i++)
        {
            _threads.push_back(new Thread(i, routine, this));
        }
        pthread_mutex_init(&_lock, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &other) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &other) = delete;

public:
    // 提供一个函数创建对象
    // 想获取对象就只有调用这个函数
    static ThreadPool<T> *getThreaedPool(int num = g_thread_num)
    {
        // 防止出现:大量的申请和释放锁的行为,而导致的无用且浪费资源的行为。
        if (nullptr == _thread_ptr)
        {
            lockGuard lockguard(&mutex);
            // 由于_thread_ptr是由static修饰的 - 只有一份
            if (nullptr == _thread_ptr)
            {
                _thread_ptr = new ThreadPool<T>(num);
            }
        }
        return _thread_ptr; // 返回的永远都是一个线程池对象
    }

    // 1.run - 将线程跑起来
    void run()
    {
        for (auto &iter : _threads)
        {
            iter->start();
            // std::cout << iter->name() << " 启动成功" << std::endl;
            logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");
        }
    }

    // 未来所有执行流所执行的方法 - 核心取任务、执行任务的逻辑
    static void *routine(void *args) // 因为在类当中,如果是一个成员方法,其会有一个隐藏的参数this指针,所以我们需要使用static进行修饰。
    {
        ThreadData *td = (ThreadData *)args;
        ThreadPool<T> *tp = (ThreadPool<T> *)td->_args;
        while (true)
        {
            T task; // 对应任务的对象
            {
                // lock
                lockGuard lockguard(tp->getMutex());

                // while(task_queue_.empty()) wait();
                while (tp->isEmpty())
                    tp->waitCond();

                // 获取任务 - 100%有任务
                task = tp->getTask(); // 任务队列是共享的->将任务从共享,拿到自己的私有空间

            } // 自动释放锁

            // 处理任务
            task(td->_name); // 要求每一个任务都要提供一个仿函数
        }
    }

    // 2.pushTask - 将任务放到任务池里
    void pushTask(const T &task)
    {
        lockGuard lockguard(&_lock); // 加锁
        _task_queue.push(task);      // 压入任务
        pthread_cond_signal(&_cond); // 唤醒线程
    }                                // 自动释放锁

    // void join()
    // {
    //     for (auto &iter : _threads)
    //     {
    //         iter->join();
    //     }
    // }

    ~ThreadPool()
    {
        for (auto &iter : _threads)
        {
            iter->join();
            delete iter;
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

private:
    std::vector<Thread *> _threads;
    int _num;
    std::queue<T> _task_queue;

    static ThreadPool<T> *_thread_ptr;
    static pthread_mutex_t _mutex;

    pthread_mutex_t _lock;
    pthread_cond_t _cond;
};

// 通过定义一个静态的指针,来在类外来对静态成员进行初始化
template <class T>
ThreadPool<T> *ThreadPool<T>::_thread_ptr = nullptr;

template <typename T>
pthread_mutex_t ThreadPool<T>::_mutex = PTHREAD_MUTEX_INITIALIZER;

testMain.cc

#include "threadPool.hpp"
#include "Task.hpp"
#include "log.hpp"
#include <ctime>
#include <iostream>

int main()
{
    logMessage(NORMAL, "%s %d %c %f\n", "这是一条日志信息", 1234, 'a', 3.14);
    srand((unsigned long)time(nullptr) ^ getpid()); // 利用 ^ getpid()让数据更随机

    // 运行一个线程库
    ThreadPool<Task>::getThreaedPool()->run();

    while (true)
    {
        // 生产的过程(制作任务的时候,要花时间)
        int x = rand() % 1000;
        usleep(5000); // 模拟制作任务的时候,花费的时间
        int y = rand() % 1000;

        Task t(x, y, [](int x, int y)->int{
            return x + y;
        });

        // std::cout << "制作任务完成" << std::endl;
        logMessage(DEBUG, "制作任务完成: %d+%d=?", x, y);
        
        // 推送任务到线程池中
        ThreadPool<Task>::getThreaedPool()->pushTask(t);

        sleep(1); // 防止刷新过快
    }
    
    return 0;
}

STL、智能指针和线程安全

#问: STL中的容器是否是线程安全的?
         不是 。原因是,STL 的设计初衷是将性能挖掘到极致 而一旦涉及到加锁保证线程安全 会对性能造成巨大的影响 。而且对于不同的容器, 加锁方式的不同, 性能可能也不同 (例如:hash表的锁表和锁桶) 。因此 STL 默认不是线程安全, 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全。
#问: 智能指针是否是线程安全的?
        对于 unique_ptr 由于只是在当前代码块范围内生效 (和对象是强绑定的,只能和一个对象相关联,在一个线程的上下文当中使用) 因此 不涉及线程安全问题 (但是也有可能会被修饰为全局,所以不是很全面的说)
        对于 shared_ptr 多个对象需要共用一个引用计数变量, 所以会 存在线程安全问题 。但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证 shared_ptr 能够高效,原子的操作引用计数。虽然 shared_ptr 里面的操作是原子的,但是我们用的不是 shared_ptr 本身,而是它所指向的对象,并不能保证所指向的对象是线程安全的。所以还是该加锁加锁……。

其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作(Compare and Swap)当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁:公平锁,非公平锁?

系统编程中的锁

        我们通过加锁的方案保证临界区的安全,就是:串行化 + 互斥。申请锁成功进入访问,申请锁失败阻塞挂起(执行流被挂起阻塞)(新的PCB被投入到锁的等待队列当中,并且将PCB的状态由R改变)

自旋锁

        循环的对一个锁进行申请(看这个锁是否好了),就是轮询检测,轮询检测就叫做自旋。自旋锁:本质就是通过不断检测锁状态,来进行资源是否就绪的方案。

#问:什么时候使用自旋锁?

         取决于资源就绪的时间问题(时间长采用挂起等待,时间短采用轮询检测),核心就在于:在临界区中执行的时长。

#问:怎么使用自旋锁?

#include <pthread.h>

// 释放锁
int pthread_spin_destroy(pthread_spinlock_t *lock);
// 创建锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

#include <pthread.h>

// 加锁
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

#include <pthread.h>

// 解锁
int pthread_spin_unlock(pthread_spinlock_t *lock);

读者写者问题

读写锁

        在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

        读者写者模型不同于生产消费模型,我们可以理解为一个正在刊登的黑板报,对于黑板报的内容是只能一个写者来进行书写,但是对于黑板报的内容是可以一堆读者来一起读的,但是并不能在写者未书写完的时候进行读取。

Note:

        写独占,读共享,读锁优先级高。

  • 读者与读者:共享关系、没有关系(各读各的)
  • 写者与写者:互斥关系。
  • 读者与写者:互斥且同步的关系。

#问:读者写者 vs 生产消费的本质区别?

        消费者会取走数据,读者不会取走数据。

伪代码

        没有实际意义,但是增强理解。

Note:

        读者优先,写者优先问题:根据引用场景,如:数据被读取的频率非常高,而被修改的频率特别低。

读写锁的加锁、解锁过程

读写锁接口

初始化与销毁

#include <pthread.h>

// 初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);

// 销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁和解锁

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值