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++多线程环境下的单例类对象创建 的相关文章

  • JSON.Net 反序列化返回“null”

    我正在使用 JSON Net 反序列化 JSON 字符串 JSON 字符串是 string testJson Fruits Apple color red size round Orange Pro
  • MFC CList 支持复制分配吗?

    我在 MSVC 中查找了 CList 定义afxtempl h http www cppdoc com example mfc classdoc MFC AFXTEMPL H html并记录在MSDN http msdn microsoft
  • 在 ASP.NET MVC 中将模型从视图传递到控制器

    我正在 ASP NET MVC 中开发我的第一个应用程序 但遇到了一个我无法解决的问题 即使在阅读了整个互联网之后也是如此 因此 我有几个使用视图模型创建的视图 它们是报告 这些视图模型是根据用户选择标准填充的 我正在尝试构建一种接受模型并
  • C 中“complex”的默认类型

    根据我读过的文档 C99 和更高版本的支持float complex double complex and long double complex作为复杂类型 但是 此代码在使用时编译时不会发出警告gcc Wall Wextra inclu
  • 全局使用和 .NET Standard 2.0

    我最近意识到我可以使用 C 10 功能文件范围的命名空间在 NET Standard 2 0 项目中也可以通过设置
  • 带有运算符语法的错误消息,但不带有函数语法的错误消息

    为什么我在调用 unary 时收到错误消息 使用运算符语法 如果我用函数语法调用它就可以了 现场演示 https godbolt org z j7AbeQ template
  • 如何在win32中使用GetSaveFileName保存文件?

    我编写此代码是为了获取 fileName 来保存我的文件 include stdafx h include
  • 在 C# 中何时使用 ArrayList 而不是 array[]?

    我经常使用一个ArrayList而不是 正常 array 当我使用时 我感觉好像我在作弊 或懒惰 ArrayList 什么时候可以使用ArrayList在数组上 数组是强类型的 并且可以很好地用作参数 如果您知道集合的长度并且它是固定的 则
  • 如何使用递归查找数字中的最小元素 [C]

    好的 所以我正在准备我的 C 考试 当谈到递归时我有点卡住了我是大学一年级的学生 这对我来说似乎有点困难 练习要求在给定的数字中使用递归函数我需要找到最小的元素 例如 52873 是 2 程序需要打印 2 include
  • 时间:2019-03-17 标签:c++fstream并发访问

    如果从不同的进程 线程同时访问文件会发生什么 据我所知 没有锁定文件的标准方法 只有操作系统特定的功能 就我而言 文件将被经常读取而很少写入 现在如果A打开一个文件进行读取 ifstream 并开始读取块 和B打开相同的文件进行写入 ofs
  • 无法为 wsdl 文件创建服务引用

    I have wsdl文件和xsd我本地机器上的文件 我想在项目中添加服务引用 我没有网络服务 我只有wsdl file 我收到以下错误 The document was understood but it could not be pro
  • C++ 到 C# 事件处理

    所以我有我的C WinForm 应用程序 我从中调用我的C CLI MFC dll图书馆 但也有一些events在我的 C 库上 甚至此事件也发生在该库的本机 非 CLI 部分 我需要从我的 C 应用程序调用一些代码 并获取一些有关此事件的
  • 如何使用 CSI.exe 脚本参数

    当你运行csi exe 安装了 Visual Studio 2015 update 2 您将得到以下语法 Microsoft R Visual C Interactive Compiler version 1 2 0 51106 Copyr
  • 为什么 f(i = -1, i = -1) 是未定义的行为?

    我正在读关于违反评估顺序 http en cppreference com w cpp language eval order 他们举了一个令我困惑的例子 1 如果标量对象上的副作用相对于同一标量对象上的另一个副作用是无序的 则行为未定义
  • 无法在 C# 中为 EventArgs 分配使用派生类型的事件处理程序

    所以我有一个事件声明如下 public event EventHandler OnChangeDetected 然后我有以下处理程序被分配给该事件 myObject OnChangeDetected OnTableChanged 我的理解是
  • Linq.Select() 中的嵌套表达式方法调用

    I use Select i gt new T 每次手动点击数据库后将我的实体对象转换为 DTO 对象 以下是一些示例实体和 DTOS 用户实体 public partial class User public int Id get set
  • printf或iostream如何指定点后的最大位数

    字符串采用什么格式printf or iomanip我应该使用 iostream 中的运算符以以下格式打印浮点数 125 0 gt 125 125 1 gt 125 1 125 12312 gt 125 12 1 12345 gt 1 12
  • 如果“嵌入式”SQL 2008 数据库文件不存在,如何创建它?

    我使用 C ADO Net 和在 Server Management Studio 中创建的嵌入式 MS SQL 2008 数据库文件 附加到 MS SQL 2008 Express 创建了一个数据库应用程序 有人可以向我指出一个资源 该资
  • 这种尺寸对齐是如何工作的

    对于所提供的评论 我无法理解以下代码 这段代码的作用是什么 以及等效的代码是什么8 aligned segment size must be 4 aligned attr gt options ssize 3 Here ssize is o
  • 如何在c linux中收听特定接口上的广播?

    我目前可以通过执行以下操作来收听我编写的简单广播服务器 仅广播 hello int fd socket PF INET SOCK DGRAM 0 struct sockaddr in addr memset addr 0 sizeof ad

随机推荐

  • 如何利用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