C++多线程环境下的单例类对象创建

2023-11-11

使用C++无锁编程实现多线程下的单例模式

贺志国
2023.8.1

在多线程环境下创建一个类的单例对象,要比单线程环境下要复杂很多。下面介绍在多线程环境下实现单例模式的几种方法。

一、尺寸较小的类单例对象创建

如果待创建的单例类SingletonForMultithread内包含的成员变量较少,整个类占用的内存空间较小,则可使用局部静态变量来创建单例对象。C++ 11标准保证在多线程环境下,在第一个线程未完成静态对象的构建前,其他线程必须等待其完成,因此是线程安全的。如果类的尺寸较大,静态变量存储栈区无法容纳该类的单例对象,则禁止使用该方法。例如:64位Linux系统默认栈的最大空间为8 MB,64位Windows系统默认栈的最大空间为1 MB,当待创建的单例对象尺寸接近或超过上述栈的默认存储空间时,如使用该方法创建则会导致程序崩溃。示例代码如下所示:

class SmallSingletonForMultithread {
 public:
  static SmallSingletonForMultithread& GetInstance() {
    static SmallSingletonForMultithread instance;
    return instance;
  }
  
 private:
  SmallSingletonForMultithread() = default;
  ~SmallSingletonForMultithread() = default;

  SmallSingletonForMultithread(const SmallSingletonForMultithread&) = delete;
  SmallSingletonForMultithread& operator=(const SmallSingletonForMultithread&) = delete;
  SmallSingletonForMultithread(SmallSingletonForMultithread&&) = delete;
  SmallSingletonForMultithread& operator=(SmallSingletonForMultithread&&) = delete;
};

二、尺寸较大的类单例对象创建(使用无锁编程来实现)

在实际工作中,由于某些单例类的尺寸较大,静态变量存储栈区无法容纳该单例对象,因此无法使用上述方法来创建单例对象,这时需要使用new在堆区动态创建单例对象。为了避免多线程环境下对于单例对象的抢夺,可使用C++无锁编程来实现。在程序结束时使用atexit(DestoryInstance)来删除单例对象,示例代码如下所示:

#include <atomic>
#include <cassert>
#include <memory>
#include <thread>
#include <vector>

namespace {
constexpr size_t kThreadNum = 2000;
}

class SingletonForMultithread {
 public:
  static SingletonForMultithread* GetInstance() {
    if (!instance_.load(std::memory_order_acquire)) {
      auto* new_ptr = new SingletonForMultithread;
      SingletonForMultithread* old_ptr = nullptr;
      if (!instance_.compare_exchange_strong(old_ptr, new_ptr,
                                             std::memory_order_release,
                                             std::memory_order_relaxed)) {
        // If the CAS operation fails, another thread has created a singleton
        // object, and it's necessary to delete the temporary object created by
        // the current thread.
        delete new_ptr;
        new_ptr = nullptr;
      }
      
      // When the program exits, the function to delete the singleton object 
      // is called.
      atexit(DestoryInstance);
    }

    return instance_.load(std::memory_order_relaxed);
  }

  static void DestoryInstance() {
    if (instance_.load(std::memory_order_acquire)) {
      auto* old_ptr = instance_.load(std::memory_order_relaxed);
      SingletonForMultithread* new_ptr = nullptr;
      if (instance_.compare_exchange_strong(old_ptr, new_ptr,
                                            std::memory_order_release,
                                            std::memory_order_relaxed)) {
        // If the CAS operation succeeds, the current thread obtains the
        // original object and can safely delete it.
        delete old_ptr;
        old_ptr = nullptr;
      }
    }
  }

 private:
  SingletonForMultithread() = default;
  ~SingletonForMultithread() = default;

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

 private:
  static std::atomic<SingletonForMultithread*> instance_;
};

// Static member variable initialization
std::atomic<SingletonForMultithread*> SingletonForMultithread::instance_;

int main() {
  std::vector<std::thread> customers;
  for (size_t i = 0; i < kThreadNum; ++i) {
    customers.emplace_back(&SingletonForMultithread::GetInstance);
  }
  for (auto& cutomer : customers) {
    cutomer.join();
  }

  auto* singleton = SingletonForMultithread::GetInstance();
  assert(singleton != nullptr);

  // singleton->DestoryInstance();

  return 0;
}

编译指令如下:

g++ -g -Wall -std=c++14  -O3 singleton_test.cpp -pthread -o singleton_test 

三、尺寸较大的类单例对象创建(使用std::unique_ptr<T>std::call_once实现)

C++ 11还支持使用std::call_once函数来确保单例对象的构造只发生一次,注意本示例中使用了智能指针std::unique_ptr<T>来管理单例对象的生命周期,示例代码如下:

#include <cassert>
#include <memory>
#include <mutex>

class SingletonForMultithread {
 public:
  ~SingletonForMultithread() = default;

  static SingletonForMultithread* GetInstance() {
    static std::unique_ptr<SingletonForMultithread> instance;
    static std::once_flag only_once;

    std::call_once(only_once,
                   []() { instance.reset(new (std::nothrow) SingletonForMultithread); });

    return instance.get();
  }

 private:
  SingletonForMultithread() = default;

  SingletonForMultithread(const SingletonForMultithread&) = delete;
  SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;
  SingletonForMultithread(SingletonForMultithread&&) = delete;
  SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
};

int main() {
  auto* singleton = SingletonForMultithread::GetInstance();
  assert(singleton != nullptr);

  return 0;
}

但我在Ubuntu 20.04系统上使用GCC 9.4.0似乎无法正常完成任务,会抛出异常,产生core dump,原因暂不详。
gcc
core dump

8月10日补充说明:

经同事路遥提示,出现core dump的根本原因是链接pthread库的指令不正确,我最开始使用的编译指令是:

g++ -g -Wall -std=c++14  -O3 singleton_test.cpp -lpthread -o singleton_test 

以上命令中,-lpthread的使用不正确,具体原因参见该博客:编译时-pthread和-lpthread之间的区别。正确的编译指令如下:

g++ -g -Wall -std=c++14  -O3 singleton_test.cpp -pthread -o singleton_test 

使用上述命令编译生成的程序能够正常运行。

四、尺寸较大的类单例对象创建(使用std::unique_ptr<T>std::atomic_flag实现)

还可使用std::atomic_flag替换std::call_once来完成任务,基本思想如下:首先定义一个静态的无锁标志变量std::atomic_flag start_flag,并将其初始值设置为ATOMIC_FLAG_INIT。第一次调用start_flag.test_and_set(std::memory_order_relaxed)函数时,由于start_flag的状态是ATOMIC_FLAG_INIT,该函数返回false,于是可调用instance.reset(new SingletonForMultithread)创建单例对象。第二次直至第N次调用start_flag.test_and_set(std::memory_order_relaxed)函数时,因为start_flag的状态已被设置,该函数返回true,创建单例对象的语句instance.reset(new SingletonForMultithread)永远不会被再次执行,这就达到了只创建一次的目的。同时,因为使用静态的智能指针变量std::unique_ptr<SingletonForMultithread> instance来管理单例对象,于是不再需要显式地回收内存,只要程序结束,静态变量自动清除,智能指针对象instance会在其析构函数中释放内存。

由于new运算符创建单例对象可能耗时较长,为了避免其他线程在单例对象创建到一半的过程中读取到不完整的对象,导致未定义的行为,我们使用另一个原子变量std::atomic<bool> finished来确保创建动作已正确完成,不选用另一个无锁标志变量std::atomic_flag的原因是,该类在C++ 20标准前未提供单独的测试函数testfinished.store(true, std::memory_order_release);while (!finished.load(std::memory_order_acquire))的内存顺序,实现了synchronizes-withhappens-before关系,保证在while (!finished.load(std::memory_order_acquire))成功时,instance.reset(new SingletonForMultithread);必定执行完毕,单例对象的创建是完整的。

完整的示例代码如下:

#include <atomic>
#include <cassert>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>

using namespace std::chrono_literals;

namespace {
constexpr size_t kThreadNum = 2000;
}

class SingletonForMultithread {
 public:
  ~SingletonForMultithread() = default;

  static SingletonForMultithread* GetInstance() {
    static std::unique_ptr<SingletonForMultithread> instance;
    static std::atomic_flag start_flag = ATOMIC_FLAG_INIT;
    static std::atomic<bool> finished(false);

    if (!finished.load(std::memory_order_acquire)) {
      // 'start_flag.test_and_set' is a completed or incomplete atomic
      // operation. It is a one-time read-modify-write operation. If it
      // completes, the modified value must have been written to the memory.
      // Because its result does not need to be synchronized with other threads,
      // the simplest memory order is used: memory_order_relaxed
      if (!start_flag.test_and_set(std::memory_order_relaxed)) {
        // The object created by the `new` operator may be relatively large and
        // time-consuming, therefore another atomic variable 'finished' is used
        // to ensure that other threads read a fully constructed singleton
        // object. Do not consider using another `std::atomic_flag`. Because it
        // doesn't provide a separate `test` function before the C++20 standard.
        instance.reset(new (std::nothrow) SingletonForMultithread);
        finished.store(true, std::memory_order_release);
      } else {
        // Wait in a loop until the singleton object is fully created, using
        // `std::this_thread::yield()` to save CPU resources.
        while (!finished.load(std::memory_order_acquire)) {
          std::this_thread::yield();
        }
      }
    }

    return instance.get();
  }

 private:
  SingletonForMultithread() {
    // Simulate a constructor that takes a relative long time.
    std::this_thread::sleep_for(10ms);
  }

  SingletonForMultithread(const SingletonForMultithread&) = delete;
  SingletonForMultithread& operator=(const SingletonForMultithread&) = delete;
  SingletonForMultithread(SingletonForMultithread&&) = delete;
  SingletonForMultithread& operator=(SingletonForMultithread&&) = delete;
};

int main() {
  std::vector<std::thread> customers;
  for (size_t i = 0; i < kThreadNum; ++i) {
    customers.emplace_back(&SingletonForMultithread::GetInstance);
  }
  for (auto& cutomer : customers) {
    cutomer.join();
  }

  auto* singleton = SingletonForMultithread::GetInstance();
  assert(singleton != nullptr);

  return 0;
}

原子变量之间的内存顺序示意图如下:

memory order

测试代码中,我们特意在构造函数中添加了让当前线程休眠10 ms的代码:std::this_thread::sleep_for(10ms);, 以此来模拟一个耗时较长的构造过程。另外,通过2000个线程来同时访问SingletonForMultithread::GetInstance(),通过下图的调试界面可看出,在SingletonForMultithread()比较漫长的构建过程中,确实有多个线程闯入,这时等待构建过程完成的代码:

    while (!finished.load(std::memory_order_acquire)) {
      std::this_thread::yield();
    }

确实发挥出了应有的拦截作用,整个过程是线程安全的。

debug
SingletonForMultithread* GetInstance()函数在ARM 64平台下使用GCC 9.4带-O3优化选项生成的汇编代码如下:

SingletonForMultithread::GetInstance():
        push    {r4, r5, r6, lr}
        ldr     r4, .L35
        ldrb    r5, [r4]        @ zero_extendqisi2
        bl      __sync_synchronize
        tst     r5, #1
        beq     .L31
.L10:
        ldrb    r5, [r4, #8]    @ zero_extendqisi2
        bl      __sync_synchronize
        cmp     r5, #0
        beq     .L32
.L19:
        ldr     r0, [r4, #4]
        pop     {r4, r5, r6, pc}
.L31:
        mov     r0, r4
        bl      __cxa_guard_acquire
        cmp     r0, #0
        beq     .L10
        ldr     r2, .L35+4
        ldr     r1, .L35+8
        add     r0, r4, #4
        bl      __aeabi_atexit
        mov     r0, r4
        bl      __cxa_guard_release
        ldrb    r5, [r4, #8]    @ zero_extendqisi2
        bl      __sync_synchronize
        cmp     r5, #0
        bne     .L19
.L32:
        mov     r6, r4
        ldrb    r3, [r6, #12]!  @ zero_extendqisi2
.L13:
        lsl     r1, r3, #24
        asr     r1, r1, #24
        mov     r2, #1
        mov     r0, r6
        mov     r5, r3
        bl      __sync_val_compare_and_swap_1
        and     r2, r5, #255
        mov     r3, r0
        and     r1, r3, #255
        cmp     r1, r2
        bne     .L13
        cmp     r2, #0
        bne     .L14
        b       .L33
.L34:
        bl      __gthrw_sched_yield()
.L14:
        ldrb    r5, [r4, #8]    @ zero_extendqisi2
        bl      __sync_synchronize
        cmp     r5, #0
        beq     .L34
        ldr     r0, [r4, #4]
        pop     {r4, r5, r6, pc}
.L33:
        ldr     r1, .L35+12
        mov     r0, #1
        bl      operator new(unsigned int, std::nothrow_t const&)
        subs    r5, r0, #0
        beq     .L18
        bl      SingletonForMultithread::SingletonForMultithread() [complete object constructor]
.L18:
        ldr     r0, [r4, #4]
        str     r5, [r4, #4]
        cmp     r0, #0
        beq     .L17
        mov     r1, #1
        bl      operator delete(void*, unsigned int)
.L17:
        bl      __sync_synchronize
        mov     r3, #1
        strb    r3, [r4, #8]
        ldr     r0, [r4, #4]
        pop     {r4, r5, r6, pc}
        ldr     r1, .L35+12
        mov     r0, r5
        bl      operator delete(void*, std::nothrow_t const&)
        bl      __cxa_end_cleanup
.L35:
        .word   .LANCHOR0
        .word   __dso_handle
        .word   _ZNSt10unique_ptrI23SingletonForMultithreadSt14default_deleteIS0_EED1Ev
        .word   _ZSt7nothrow

说明:r0-r15是ARMv7 32位架构下的通用寄存器,每个寄存器的空间为4字节(32位),其中r13又称为sp(栈顶指针),r14 又称为lr(链接寄存器,即当前调用结束后的返回地址),r15又称为pc(程序计数器);在ARMv8 64位架构下,通用寄存器为x0-x30,每个寄存器的空间为8字节(64位),w0-w30分别表示对应的x0-x30的低32位寄存器。在ARMv8 64位架构下,x0 = r0, x1 = r1, …, x15 = r15

上述代码首先从.L35加载内容到寄存器r4,再将其转存到寄存器r5, 然后加载 r5 寄存器中的一个字节,这个字节表示单例对象是否已经被创建。然后使用 __sync_synchronize 函数进行同步,确保前面的加载操作完成。接下来,代码通过比较 r5 是否为 1(已创建)来决定是继续执行还是等待。如果已经创建,则直接跳到 .L19,否则继续执行。在 .L31 标签处,代码调用 __cxa_guard_acquire 函数来获取锁,确保只有一个线程可以进入临界区。如果没有获取到锁,则跳回 .L10,继续尝试获取锁。在获取到锁之后,代码进行一系列操作,包括调用 __aeabi_atexit 函数注册析构函数,调用 __cxa_guard_release 函数释放锁,并将单例对象的地址存储在 r0 中。然后,代码再次加载 r5 寄存器中的一个字节,进行同步,并检查 r5 是否为 0。如果为 0,则跳转到 .L32 标签处,继续尝试获取锁。否则,继续执行。在 .L32 标签处,代码使用 __sync_val_compare_and_swap_1 函数来进行原子比较和交换操作,以确保只有一个线程可以成功创建单例对象。之后,代码进行一系列操作,包括调用构造函数和析构函数,分配和释放内存等。最后,代码进行一系列的清理操作,并将单例对象的地址存储在 r0 中,最终返回该地址。

编译命令如下:

g++ -g -Wall -std=c++14  -O3 singleton_test.cpp -pthread -o singleton_test 

如果使用CMake编译,配置文件CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.0.0)
project(singleton_test VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 14)

# If the debug option is not given, the program will not have debugging information.
SET(CMAKE_BUILD_TYPE "Debug")

add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp)

find_package(Threads REQUIRED)
set(THREADS_PREFER_PTHREAD_FLAG ON)
# target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(${PROJECT_NAME} Threads::Threads)

include(CTest)
enable_testing()
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

编译指令为:

# Do it only once
mkdir build
cd build
cmake .. && make
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++多线程环境下的单例类对象创建 的相关文章

  • 每个托管线程是否都有自己对应的本机线程?

    我想知道是否在 Net 中创建托管线程 通过调用Thread Start 导致在后台创建一个本机线程 那么托管线程是否有对应的本机线程呢 如果是 当托管线程等待或睡眠时 是否意味着相应的本机线程也在等待或睡眠 是的 NET 线程映射到所有当
  • 注销租约抛出 InvalidOperationException

    我有一个使用插件的应用程序 我在另一个应用程序域中加载插件 我使用 RemoteHandle 类http www pocketsilicon com post Things That Make My Life Hell Part 1 App
  • 计算 Richtextbox 中所有单词的最有效方法是什么?

    我正在编写一个文本编辑器 需要提供实时字数统计 现在我正在使用这个扩展方法 public static int WordCount this string s s s TrimEnd if String IsNullOrEmpty s re
  • 在 DataView 的 RowFilter 中选择 DISTINCT

    我试图根据与另一个表的关系缩小 DataView 中的行范围 我使用的 RowFilter 如下 dv new DataView myDS myTable id IN SELECT DISTINCT parentID FROM myOthe
  • 复制 std::function 的成本有多高?

    While std function是可移动的 但在某些情况下不可能或不方便 复制它会受到重大处罚吗 它是否可能取决于捕获变量的大小 如果它是使用 lambda 表达式创建的 它依赖于实现吗 std function通常被实现为值语义 小缓
  • 在 LINQ 中按 Id 连接多表和分组

    我想按categoryId显示列表产品的名称组 这是我的代码 我想要我的视图显示结果 Desktop PC HP Red PC Dell Yellow PC Asus Red SmartPhone Lumia 720 Blue 我的组模型
  • 为什么极端下派生类(多重虚拟继承)的大小包括超类成员大小的两倍?

    include
  • 使用 Newtonsoft 和 C# 反序列化嵌套 JSON

    我正在尝试解析来自 Rest API 的 Json 响应 我可以获得很好的响应并创建了一些类模型 我正在使用 Newtonsoft 的 Json Net 我的响应中不断收到空值 并且不确定我的模型设置是否正确或缺少某些内容 例如 我想要获取
  • 单个对象的 Monogame XNA 变换矩阵?

    我读过一些解释 XNA Monogame 变换矩阵的教程 问题是这些矩阵应用于 SpriteBatch Begin matrix 这意味着所有 Draw 代码都将被转换 如何将变换矩阵应用于单个可绘制对象 就我而言 我想转换滚动背景 使其自
  • 如何创建包含 IPv4 地址的文本框? [复制]

    这个问题在这里已经有答案了 如何制作一个这样的文本框 我想所有的用户都见过这个并且知道它的功能 您可以使用带有 Mask 的 MaskedTestBox000 000 000 000 欲了解更多信息 请参阅文档 http msdn micr
  • 为什么 Google 测试会出现段错误?

    我是 Google Test 的新手 正在尝试提供的示例 我的问题是 当我引入失败并设置GTEST BREAK ON FAILURE 1 或使用命令行选项 GTest 将出现段错误 我正在考虑这个例子 https code google c
  • 回发后刷新时提示确认表单重新提交。我做错了什么?

    我有一个以空白 默认状态启动的仪表板 我让用户能够将保存的状态加载到仪表板中 当他们单击 应用 按钮时 我运行以下代码 function CloseAndSave var radUpload find radUpload1ID var in
  • qdbusxml2cpp 未知类型

    在使用 qdbusxml2cpp 程序将以下 xml 转换为 Qt 类时 我收到此错误 qdbusxml2cpp c ObjectManager a ObjectManager ObjectManager cpp xml object ma
  • C#:帮助理解 UML 类图中的 <>

    我目前正在做一个项目 我们必须从 UML 图编写代码 我了解 UML 类图的剖析 但我无法理解什么 lt
  • 为什么 std::strstream 被弃用?

    我最近发现std strstream已被弃用 取而代之的是std stringstream 我已经有一段时间没有使用它了 但它做了我当时需要做的事情 所以很惊讶听到它的弃用 我的问题是为什么做出这个决定 有什么好处std stringstr
  • CMake 无法确定目标的链接器语言

    首先 我查看了this https stackoverflow com questions 11801186 cmake unable to determine linker language with c发帖并找不到解决我的问题的方法 我
  • 动态添加 ASP.Net 控件

    我有一个存储过程 它根据数据库中存储的记录数返回多行 现在我想有一种方法来创建 div 带有包含该行值的控件的标记 如果从数据库返回 10 行 则 10 div 必须创建标签 我有下面的代码来从数据库中获取结果 但我不知道如何从这里继续 S
  • Cmake 链接共享库:包含库中的头文件时“没有这样的文件或目录”

    我正在学习使用 CMake 构建库 构建库的代码结构如下 include Test hpp ITest hpp interface src Test cpp ITest cpp 在 CMakeLists txt 中 我用来构建库的句子是 f
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成
  • C++ 函数重载类似转换

    我收到一个错误 指出两个重载具有相似的转换 我尝试了太多的事情 但没有任何帮助 这是那段代码 CString GetInput int numberOfInput BOOL clearBuffer FALSE UINT timeout IN

随机推荐

  • 如何利用Python自动发邮件

    在工作中 每天或者每周结束的时候我们都会发送相应的日报或者周报给上级领导 来汇报你做了那些工作 可是汇报工作内容的时候我们始终都是打开邮箱 写入内容 发送和抄送给固定的人 那么这么繁琐并且重复的一件事 我们能不能使用程序来简化 答案是可以的
  • SqlServer数据库中文乱码

    可以在建立数据时指定排序规则 记得选中文简体 Chinese PRC CS AI WS 如果数据库中已经有数据 则转换 编码会失败
  • Android Studio编译异常Error: Program type already present: android.support.design.widget.CoordinatorLayo

    记录一下 希望能帮到小伙伴 解决的方案在build gradle修改 implementation com android support design 25 1 0 修改为 implementation com android suppo
  • Centos6 升级glibc-2.17,解决Requires: libc.so.6(GLIBC_2.17)(64bit)错误解决方法

    在Centos6安装mysql 8 0 33系列提示错误如下 root rhel64 Downloads rpm ivh mysql community common 8 0 33 1 el6 x86 64 rpm gt mysql com
  • frp内网穿透实验

    Frp Fast Reverse Proxy 是比较流行的一款 FRP 是一个免费开源的用于内网穿透的反向代理应用 它支持 TCP UDP 协议 也为 http 和 https 协议提供了额外的支持 你可以粗略理解它是一个中转站 帮你实现
  • 如何用Java对Excel表进行读写操作?

    博主公众号 没有腹肌的程序猿 公众号会不定期更新一些数据集 有需要的可以点点关注哦 如何用Java对Excel表进行读写操作 1 Java读取Excel表的内容 Java读取Excel表相对来说还是比较简单的 分为3步 首先是先读取文件 再
  • 浮动的特点

    一 什么是浮动 1 浮动概念 是一种布局方式 可以让元素脱离文档流 一旦元素脱离文档流 就不再具有元素在文档流中的特点 从而帮助我们布局 2 设置浮动 float样式名 可选值 none 不浮动 默认值 left 向左浮动 right 向右
  • Python 描述符简述

    Python 中 通过使用描述符 可以让程序员在引用一个对象属性时自定义要完成的工作 本质上看 描述符就是一个类 只不过它定义了另一个类中属性的访问方式 换句话说 一个类可以将属性管理全权委托给描述符类 描述符是 Python 中复杂属性访
  • Django TypeError: Abstract models cannot be instantiated.错误解决方案

    问题 2023 09 05 10 23 41 dvadmin utils exception CustomExceptionHandler 64 ERROR Traceback most recent call last File D In
  • Content-Length如何计算

    我还没明白原理不过这代码可以实现 可以用 有时间再看原理 import requests def get content length data length len data keys 2 1 total join list data k
  • 析构函数和虚函数的用法和作用

    析构函数和虚函数的用法和作用 1 析构函数 1 1 特点 2 虚函数 2 1 功能 2 2 使用方法 2 3 纯虚函数 2 3 1 意义 1 析构函数 析构函数是特殊的类函数 没有返回类型 没有参数 不能随意调用 也没有重载 在类对象生命期
  • 什么是IO Pad?

    1 什么是IO pad IO pad是一个芯片管脚处理模块 即可以将芯片管脚的信号经过处理送给芯片内部 又可以将芯片内部输出的信号经过处理送到芯片管脚 输入信号处理包含时钟信号 复位信号等 输出信号包含观察时钟 中断等 IO pad模块可以
  • C++坑总结

    const typedef struct ElemType elem int Tablelen SSTable void change const SSTable ST int i 0 for i 0 i lt ST gt Tablelen
  • MD5加密解密

    md5加密 采用MD5加密解密 MD5加码 生成32位md5码 public static String string2MD5 String inStr MessageDigest md5 null try md5 MessageDiges
  • vue-admin-template

    vue element admin 介绍 vue element admin是一个后台前端解决方案 它基于 vue 和 element ui实现 它使用了最新的前端技术栈 内置了 i18 国际化解决方案 动态路由 权限验证 提炼了典型的业务
  • Android时间戳与字符串相互转换

    import java text ParseException import java text SimpleDateFormat import java util Date public class TestTime public sta
  • unity修改sprite大小的方法

    unity怎么修改sprite的大小呢 方法就是修改pixel per unit的值 值越大 sprite就越小
  • 【机器学习】线性回归【上】朴素最小二乘估计

    有任何的书写错误 排版错误 概念错误等 希望大家包含指正 由于字数限制 分成两篇博客 机器学习 线性回归 上 朴素最小二乘估计 机器学习 线性回归 下 正则化最小二乘估计 提醒 下文中的 alpha 和 lambda
  • Maven插件仓库地址

    以下是Maven插件地址
  • C++多线程环境下的单例类对象创建

    使用C 无锁编程实现多线程下的单例模式 贺志国 2023 8 1 在多线程环境下创建一个类的单例对象 要比单线程环境下要复杂很多 下面介绍在多线程环境下实现单例模式的几种方法 一 尺寸较小的类单例对象创建 如果待创建的单例类Singleto