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

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

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

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

1 懒汉式

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

cpp 复制代码
#include <iostream>
#include <mutex>

class Test {
public:
  static Test *GetInstance() {
    std::lock_guard<std::mutex> lock(mtx);
    if (instance == nullptr) {
      instance = new Test();
      return instance;
    }
    return instance;
  };

  Test(const Test &) = delete;
  Test &operator=(const Test &) = delete;

  ~Test() {
    std::cout << "~Test()" << std::endl;
  };

  class Recycler {
  public:
    ~Recycler() {
      if (Test::instance) {
        delete Test::instance;
      } else {
        std::cout << "no need to recycle" << std::endl;
      }
    }
  };
  static Recycler recycler;

  void Do() {
    std::cout << "Do()" << std::endl;
  }

private:
  static Test *instance;
  static std::mutex mtx;
  Test() {
    std::cout << "Test()" << std::endl;
  };
};

Test *Test::instance = nullptr;
std::mutex Test::mtx;
Test::Recycler recycler;

void TestDo(Test test) {
  test.Do();
}

int main() {
  Test *test = Test::GetInstance();
  test->Do();
  return 0;
}

特点:

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

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

注意点:

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

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

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

static Test *instance;

static std::mutex mtx;

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

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

2 饿汉式

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

cpp 复制代码
#include <iostream>
#include <mutex>

class Test {
public:
  static Test *GetInstance() {
    return instance;
  };

  Test(const Test &) = delete;
  Test &operator=(const Test &) = delete;

  ~Test() {
    std::cout << "~Test()" << std::endl;
  };

  class Recycler {
  public:
    ~Recycler() {
      if (Test::instance) {
        delete Test::instance;
      } else {
        std::cout << "no need to recycle" << std::endl;
      }
    }
  };
  static Recycler recycler;

  void Do() {
    std::cout << "Do()" << std::endl;
  }

private:
  static Test *instance;
  Test() {
    std::cout << "Test()" << std::endl;
  };
};

Test *Test::instance = new Test();
Test::Recycler recycler;

char *p = (char *)malloc(1024);

int main() {
  printf("main start\n");
  Test *test = Test::GetInstance();
  test->Do();
  printf("p: %p\n", p);
  p[0] = 1;
  return 0;
}

题外话:

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

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

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

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

const char *p = (char *)malloc(1024);
int main() {
  printf("p: %p\n", p);
  p[0] = 1;
  return 0;
}

3 cyberrt 中 TimingWheel 单例实现

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

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

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

cpp 复制代码
#ifndef DISALLOW_COPY_AND_ASSIGN
#define DISALLOW_COPY_AND_ASSIGN(classname) \
  classname(const classname &) = delete;    \
  classname &operator=(const classname &) = delete;
#endif

#ifndef DECLARE_SINGLETON
#define DECLARE_SINGLETON(classname)                                        \
 public:                                                                    \
  static classname *instance(bool create_if_needed = true) {                \
    static classname *inst = nullptr;                                       \
    if (!inst && create_if_needed) {                                        \
      static std::once_flag flag;                                           \
      std::call_once(flag, [&] { inst = new (std::nothrow) classname(); }); \
    }                                                                       \
    return inst;                                                            \
  }                                                                         \
                                                                            \
  static void clean_up() {                                                  \
    auto inst = instance(false);                                            \
    if (inst != nullptr) {                                                  \
      call_shut_down(inst);                                                 \
    }                                                                       \
  }                                                                         \
                                                                            \
 private:                                                                   \
  classname();                                                              \
  DISALLOW_COPY_AND_ASSIGN(classname)
#endif
相关推荐
im_AMBER2 分钟前
Leetcode 102 反转链表
数据结构·c++·学习·算法·leetcode·链表
Coder_Boy_7 分钟前
基于SpringAI的在线考试系统-企业级软件研发工程应用规范实现细节
大数据·开发语言·人工智能·spring boot
lly20240610 分钟前
SQL SELECT 语句详解
开发语言
今儿敲了吗23 分钟前
01|多项式输出
c++·笔记·算法
程序员Jared25 分钟前
C++11—mutex
c++
superman超哥32 分钟前
Rust 异步时间管理核心:Tokio 定时器实现机制深度剖析
开发语言·rust·编程语言·rust异步时间管理核心·tokio定时器实现机制·tokio定时器
朔北之忘 Clancy33 分钟前
2025 年 9 月青少年软编等考 C 语言一级真题解析
c语言·开发语言·c++·学习·数学·青少年编程·题解
玛丽莲茼蒿36 分钟前
javaSE 集合框架(五)——java 8新品Stream类
java·开发语言
wjs202440 分钟前
SQLite Glob 子句详解
开发语言
youyicc1 小时前
Qt连接Pg数据库
开发语言·数据库·qt