• [c++] 单例模式 + cyberrt TimingWheel 单例分析


    单例模式要求一个类在一个进程中只能创建一个对象。比如 cyberrt 中的 TimingWheel 类就是单例模式,这个类管理着一个进程内的所有定时器,只需要一个对象就可以。

    单例模式的实现有两种方式,懒汉式和饿汉式。懒汉式,当第一次使用的时候才会真正创建这个对象;饿汉式,不管会不会用到这个对象,在进程启动的时候都会创建这个对象,如果一直不使用,那么就会造成资源浪费。饿汉式的缺点是可能造成资源浪费,但是对性能友好,因为在进程启动的时候就直接创建了,需要使用的时候可以直接拿来使用;懒汉式反之。

    在工作中一般使用懒汉式。

    1 懒汉式

    懒汉式示例代码如下,在如下代码中实现了自动回收的机制,通过内部的类 Recycler 来完成。

    1. #include
    2. #include
    3. class Test {
    4. public:
    5. static Test *GetInstance() {
    6. std::lock_guard lock(mtx);
    7. if (instance == nullptr) {
    8. instance = new Test();
    9. return instance;
    10. }
    11. return instance;
    12. };
    13. Test(const Test &) = delete;
    14. Test &operator=(const Test &) = delete;
    15. ~Test() {
    16. std::cout << "~Test()" << std::endl;
    17. };
    18. class Recycler {
    19. public:
    20. ~Recycler() {
    21. if (Test::instance) {
    22. delete Test::instance;
    23. } else {
    24. std::cout << "no need to recycle" << std::endl;
    25. }
    26. }
    27. };
    28. static Recycler recycler;
    29. void Do() {
    30. std::cout << "Do()" << std::endl;
    31. }
    32. private:
    33. static Test *instance;
    34. static std::mutex mtx;
    35. Test() {
    36. std::cout << "Test()" << std::endl;
    37. };
    38. };
    39. Test *Test::instance = nullptr;
    40. std::mutex Test::mtx;
    41. Test::Recycler recycler;
    42. void TestDo(Test test) {
    43. test.Do();
    44. }
    45. int main() {
    46. Test *test = Test::GetInstance();
    47. test->Do();
    48. return 0;
    49. }

    特点:

    (1)第一次使用对象的时候才会创建,懒加载模式。懒加载思想很常见,比如 linux 中用户态的内存管理,就是典型的懒加载。

    (2)在 GetInstance() 需要加锁,如果多线程频繁调用,会影响性能。个人认为这个只是理论上的缺点,真正使用中,单例模式很少有多线程频繁调用的情况。

    注意点:

    (1)在 GetInstance() 中需要加锁。

    (2)如下两个静态成员变量需要在类的外部初始化

    类的静态变量需要在类外部初始化,这是静态变量和非静态变量的明显区别。

      static Test *instance;
      static std::mutex mtx;

    (3)拷贝构造函数和赋值运算符需要禁用

    如果不禁用,通过拷贝构造函数和赋值运算符可以生成新的对象,就不能保证单例了。

    2 饿汉式

    不管将来用不用,这个对象都会创建好。

    1. #include
    2. #include
    3. class Test {
    4. public:
    5. static Test *GetInstance() {
    6. return instance;
    7. };
    8. Test(const Test &) = delete;
    9. Test &operator=(const Test &) = delete;
    10. ~Test() {
    11. std::cout << "~Test()" << std::endl;
    12. };
    13. class Recycler {
    14. public:
    15. ~Recycler() {
    16. if (Test::instance) {
    17. delete Test::instance;
    18. } else {
    19. std::cout << "no need to recycle" << std::endl;
    20. }
    21. }
    22. };
    23. static Recycler recycler;
    24. void Do() {
    25. std::cout << "Do()" << std::endl;
    26. }
    27. private:
    28. static Test *instance;
    29. Test() {
    30. std::cout << "Test()" << std::endl;
    31. };
    32. };
    33. Test *Test::instance = new Test();
    34. Test::Recycler recycler;
    35. char *p = (char *)malloc(1024);
    36. int main() {
    37. printf("main start\n");
    38. Test *test = Test::GetInstance();
    39. test->Do();
    40. printf("p: %p\n", p);
    41. p[0] = 1;
    42. return 0;
    43. }

    题外话:

    从上边的代码实现中可以看出来,在 c++ 中,在函数外部是可以调用 new 来创建对象的,这种使用方式是自己很少使用的。

    并且在函数外部也可以是有 malloc() 来申请内存。

    但是在 c 中,在函数外部申请内存的话,如下代码所示,编译会报错。

    1. #include
    2. #include
    3. #include
    4. const char *p = (char *)malloc(1024);
    5. int main() {
    6. printf("p: %p\n", p);
    7. p[0] = 1;
    8. return 0;
    9. }

    3 cyberrt 中 TimingWheel 单例实现

    cyberrt 中的类 TimingWheel 使用了单例模式。TimingWheel 是一个进程内所有定时器的底层管理者。cyberrt 中实现单例的方式封装在了一个宏里边,这个宏是 DECLARE_SINGLETON,定义如下,实现主要有以下几点。

    (1)使用 std::call_once 来实现,保证了原子性

    (2)禁用了拷贝构造函数和赋值构造函数

    1. #ifndef DISALLOW_COPY_AND_ASSIGN
    2. #define DISALLOW_COPY_AND_ASSIGN(classname) \
    3. classname(const classname &) = delete; \
    4. classname &operator=(const classname &) = delete;
    5. #endif
    6. #ifndef DECLARE_SINGLETON
    7. #define DECLARE_SINGLETON(classname) \
    8. public: \
    9. static classname *instance(bool create_if_needed = true) { \
    10. static classname *inst = nullptr; \
    11. if (!inst && create_if_needed) { \
    12. static std::once_flag flag; \
    13. std::call_once(flag, [&] { inst = new (std::nothrow) classname(); }); \
    14. } \
    15. return inst; \
    16. } \
    17. \
    18. static void clean_up() { \
    19. auto inst = instance(false); \
    20. if (inst != nullptr) { \
    21. call_shut_down(inst); \
    22. } \
    23. } \
    24. \
    25. private: \
    26. classname(); \
    27. DISALLOW_COPY_AND_ASSIGN(classname)
    28. #endif
  • 相关阅读:
    Android Studio 导入自己编译的 framework.jar
    SD卡格式化怎么恢复?
    【FPGA】DDR3学习笔记(二)丨从SDRAM到DDR3的IP核设计
    go实现复杂度与简单排序算法
    第二章 第十四节:字典的概念
    数据治理体系演进简介
    Centos7下MongoDB安装到基本命令的学习
    tkwebview2使用tkinter输入框失效的解决方法
    Feign负载均衡写法
    「解析」COCO 数据读取与模型结果解析
  • 原文地址:https://blog.csdn.net/weixin_38184628/article/details/136271738