高性能的日志模块spdlog关于异步写日志模块的选择：一、日志模块easylogging++：轻量级，易用，支持异步日志，适合中小型项目，配置简单，功能较全面。spdlog：性能优异，支持异步日志，线程安全，广泛使用，支持多种日志格式和目标，社区活跃，适合对性能有较高要求的项目。muduo async log：muduo库自带的异步日志模块，设计用于高性能网络库，适合网络服务器等高并发场景，但依赖muduo库，集成成本较高。建议：如果你项目对性能和灵活性要求较高，推荐使用spdlog；如果项目较简单或想快速集成，easylogging++是不错的选择；如果你已经使用muduo库，或者项目是高性能网络服务，使用muduo async log更合适。二、spdlog的使用1、Demo#include <spdlog/spdlog.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> #include <spdlog/fmt/bin_to_hex.h> #include <thread> #include <chrono> #include <iostream> #include <iomanip> int main() { auto async_logger = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "async_log.txt"); spdlog::set_default_logger(async_logger); // 设置日志格式，添加时间戳 spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); std::cout << "主线程开始日志写入" << std::endl; spdlog::info("异步日志示例开始"); std::thread t1([]() { for (int i = 0; i < 10; ++i) { spdlog::info("线程1日志条目 {}", i); { auto now = std::chrono::system_clock::now(); std::time_t now_c = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; char time_buf[30]; std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now_c)); std::cout << "[" << time_buf << "." << std::setfill('0') << std::setw(3) << ms.count() << "] 线程1写入日志: " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } }); std::thread t2([]() { for (int i = 0; i < 10; ++i) { spdlog::info("线程2日志条目 {}", i); { auto now = std::chrono::system_clock::now(); std::time_t now_c = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; char time_buf[30]; std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now_c)); std::cout << "[" << time_buf << "." << std::setfill('0') << std::setw(3) << ms.count() << "] 线程2写入日志: " << i << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(30)); } }); t1.join(); t2.join(); spdlog::info("异步日志示例结束"); std::cout << "主线程日志写入结束" << std::endl; spdlog::shutdown(); return 0; }安装：sudo apt-get install libspdlog-dev运行：root@iZbp1est4y0mdt6ask7pgpZ:/home/peter/spdlog# g++ -std=c++11 -pthread -o spdlogtest spdlogtest.cpp终端打印：root@iZbp1est4y0mdt6ask7pgpZ:/home/peter/spdlog# ./spdlogtest 主线程开始日志写入 [2025-07-14 20:37:07.142] 线程2写入日志: 0 [2025-07-14 20:37:07.142] 线程1写入日志: 0 [2025-07-14 20:37:07.173] 线程2写入日志: 1 [2025-07-14 20:37:07.193] 线程1写入日志: 1 [2025-07-14 20:37:07.203] 线程2写入日志: 2 [2025-07-14 20:37:07.233] 线程2写入日志: 3 [2025-07-14 20:37:07.243] 线程1写入日志: 2 [2025-07-14 20:37:07.263] 线程2写入日志: 4 [2025-07-14 20:37:07.293] 线程1写入日志: 3 [2025-07-14 20:37:07.293] 线程2写入日志: 5 [2025-07-14 20:37:07.324] 线程2写入日志: 6 [2025-07-14 20:37:07.343] 线程1写入日志: 4 [2025-07-14 20:37:07.354] 线程2写入日志: 7 [2025-07-14 20:37:07.384] 线程2写入日志: 8 [2025-07-14 20:37:07.393] 线程1写入日志: 5 [2025-07-14 20:37:07.414] 线程2写入日志: 9 [2025-07-14 20:37:07.444] 线程1写入日志: 6 [2025-07-14 20:37:07.494] 线程1写入日志: 7 [2025-07-14 20:37:07.544] 线程1写入日志: 8 [2025-07-14 20:37:07.594] 线程1写入日志: 9 主线程日志写入结束 日志文件打印：
1.Google Test的介绍1.1 Why google test google test helps you write better C++ tests. google test is a testing framework developed by the Testing Technology team with Google's specific requirements and constraints in mind. No matter whether you work on Linux, Windows, or a Mac. if you write C++ code, google test can help you. And it supports any kind of tests, not just unit tests.[^google test 在 GitHub的介绍^]1.2 google test caseTest Case原意是指一个测试条件集合，这个条件集合定义了系统在此集合下的行为 一个简单的测试声明需要使用TEST宏。TEST宏有两个参数：测试用例的名称和测试的描述性名称。TEST(TestSuiteName, TestName) { ... test body ... } 根据Google的文档，测试用例是一些能共享数据的字程序的测试集合。从左到右阅读测试用例名称，可以连成一句话，描述了我们想要验证的行为。测试的描述性名称可以为开发人员快速提供一个有关代码行为的准确描述。测试的描述性名称越容易理解，开发人员能越快找到需要的东西。1.2.1 FALED TEST Google Mock给出的相关输出信息可能不太容易读懂。从最后一行开始看。如果是PASSEED，可以不用看测试的输出，因为所有测试都过了。如果是FAILED(正如示例一样)，我们可以看到有多少测试失败了。如果是其他类别的信息，则是测试程序在一个测试运行过程中崩溃了如果有一个或多个测试失败了，可以从下至上观察Google Mock输出信息，找到失败的测试。Google Mock会在每个测试名称前输出一个[RUN]，在测试失败的情况下输出[FAILED]或[OK]。失败的时候，[RUN]和[FAILED]之间的信息行可以帮助我们了解测试为什么失败#include <iostream> #include <gtest/gtest.h> using namespace std; TEST(Testdemo1, TestFailed) { ASSERT_TRUE( 1 == 2 ); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }1.3 Basic usage1.3.1 Assertions:Fatal Failure : 致命错误 ASSERT\_TRUE or ASSERT\_FALSE or ASSERT\_EQ or ASSERT\_NE or ASSERT\_STREQ 出错之后的后面语句都不会被执行，一个test case里面可以出现一个assert或者多个assert Non-Fatal Failure : 非致命错误 EXPECT\_EQ EXPECT\_NE EXPECT\_STREQ出错之后的后面语句仍可被执行tips: googletest support EXPECT\_EQ(NULL, ptr) and ASSERT\_EQ(NULL, ptr) but not EXPECT\_NE(NULL, ptr) and ASSERT\_NE(NULL, ptr)2.Gmock的介绍2.1 fixture fixture与设置Google Mock允许我们定义一个fixture类，我们可以在这个类中为相关的测试声明函数和数据从技术角度说，所有的Google Mock测试都使用一个由Google Mock自己生成的fixture。GMock会在运行每个测试时创建fixture类实例。在创建每个case之前，都会先创建一个类实例。为了使用自定义的fixture，我们把TEST宏替换为TEST_F，F表示fixture。如果忘记使用fixture，任何使用fixture类成员的测试代码都会编译失败。常用用法(使用New，别使用Old) refer to: http://google.github.io/googletest/gmock\_cook\_book.htmlSimple OldMOCK_METHOD1(Foo, bool(int))NewMOCK_METHOD(bool, Foo, (int))Const Method OldMOCK_CONST_METHOD1(Foo, bool(int))NewMOCK_METHOD(bool, Foo, (int), (const))Method in a Class Template OldMOCK_METHOD1_T(Foo, bool(int))NewMOCK_METHOD(bool, Foo, (int))Const Method in a Class Template OldMOCK_CONST_METHOD1_T(Foo, bool(int))NewMOCK_METHOD(bool, Foo, (int), (const))3.How to write UT单元测试包括一个描述性的名称和一系列代码声明，从概念上可以细分成四个(有序的)部分，我们也称之为： Arrange: Arrange everything required to run the Test（测试的初始化）---->Act: Run the test（测试的行为）----> Assert: Verify the output（怎样验证行为结果）设置能够运行上下文的语句，这一部分是可选的一条或多条能构成你想要验证的行为的语句；一条或多条验证期望输出的语句 *清理工作的语句（例如，释放所分配的内存，关闭之前打开的数据库连接），是这一部分是可选的测试行为而非方法不要把集中精力去测试成员函数。实现了一个add()成员函数，再写一个TEST(xxxx, Add)的测试。但是，写一个完全覆盖add的行为测试需要考虑多种不同的情形。结果必须将许多不同的行为进行编码进同一个测试，这样需要花费大量时间（所以不建议使用分支测试方法）相反，应该把注意力集中在行为或描述行为的情形上。(建议使用语句测试方法)可参考恒生官方单元测试培训测试结构文件组织 一致的命名约定可以让开发人员更容易找到需要的测试，一个xxx.cpp文件对应一个xxx\_unittest.cppFixtureTest Fixtures: Using the Same Data Configuration for Multiple Tests {#same-data-multiple-tests} When using a fixture, use TEST_F() instead of TEST() as it allows you to access objects and subroutines in the test fixture: TEST_F(TestFixtureName, TestName) { ... test body ... } Like TEST(), the first argument is the test suite name, but for TEST_F() this must be the name of the test fixture class. You've probably guessed: _F is for fixture. SetUp and TeardownInitializes it via SetUp(). Cleans up the fixture via TearDown(). mainint main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } The ::testing::InitGoogleTest() function parses the command line for googletest flags, and removes all recognized flags. This allows the user to control a test program’s behavior via various flags, which we’ll cover in the AdvancedGuide. You must call this function before calling RUN_ALL_TESTS(), or the flags won’t be properly initialized. when RUN_ALL_TESTS() is called, it first calls the SetUp() method of each environment object, then runs the tests if none of the environments reported fatal failures and GTEST_SKIP() was not called. RUN_ALL_TESTS() always calls TearDown() with each environment object, regardless of whether or not the tests were run. Note that RUN_ALL_TESTS() runs all tests in your link unit–they can be from different test suites, or even different source files. Google官方也给了很多demo以及怎么写googletest googletest samples.4.How to debug UT我们把执行一遍所有的测试称之为运行测试（test run或suite run）。在运行测试的过程中，工具会枚举所有的测试，独立地执行每个测试。对于每个测试来说，测试工具都是从头到尾执行其中的语句4.1 disable掉test case 在GMock中，在测试用例名称前加上DISABLE\_前缀会跳过执行测试如果遇到不能很快fix的broken test，注释掉UT代码是可以的，但更好的办法是把测试标记为禁用，显示的警用测试的功能，并且在运行所有测试时提示哪些是被禁用的，在GMock中，在测试名称前加上DISABLE\_前缀来禁用测试If you need to disable all tests in a test suite, you can either add DISABLED_ to the front of the name of each test, or alternatively add it to the front of the test suite name. For example, // Tests that Foo does Abc. TEST(FooTest, DISABLED_DoesAbc) { ... } class DISABLED_BarTest : public testing::Test { ... }; // Tests that Bar does Xyz. TEST_F(DISABLED_BarTest, DoesXyz) { ... } 4.2 跟踪测试数目有助于快速判断是否犯了以下错误运行了错误的测试集GMock过滤器忽略了新的测试测试并没有经过编译或链接测试被禁止了4.3 只运行一部分test caseRunning a Subset of the Tests By default, a googletest program runs all tests the user has defined. Sometimes, you want to run only a subset of the tests (e.g. for debugging or quickly verifying a change). If you set the GTEST_FILTER environment variable or the --gtest_filter flag to a filter string, googletest will only run the tests whose full names (in the form of TestSuiteName.TestName) match the filter. ./foo_test Has no flag, and thus runs all its tests. ./foo_test --gtest_filter=FooTest.* Runs everything in test suite FooTest . ./foo_test --gtest_filter=FooTest.TestName Runs TestName in test suite FooTest . 4.4 把结果打印信息输出到文本中 ./my\_test > gtest\_output.txt4.5 使用GDB调试5.UT代码规范总结一些测试中的不太好的使用：在写测试时，不要跑去重构。同样，在尝试让测试通过时，不要去重构。将两件事情一起做时，一旦出岔子会浪费时间。而且这肯定会发生它必须能够独立运行 而且它不依赖于任何外部输入一次只做一件事情，一个方法中有太多test case-被测试的方法做了太多事情改变一个地方，多处受影响--也许是测试的设计问题，也许是实现代码有过多依赖测试运行速度缓慢，表示当前单元测试也许在使用外部系统，例如网络、数据库、文件系统等。通常也意味着测试类有过多的职责5.1 使用命名空间using EQ()所在的命名空间是一个与测试本意无关的实现细节。隐藏这一细节可以提升测试中的抽象程度 ASSERT(encode, testing::Eq("A")); using : testing: Eq ASSERT(encode, Eq("A"));//标准库 #include <iostream> #include <vector> //第三方库 //#include <boost/shared_ptr.hpp> #include <gtest/gtest.h> #include <gmock/gmock.h> //其他编译需要的.hpp文件 //UT namespace using ::testing::AtLeast; using ::testing::Return; using ::testing::_; using ::testing::Invoke; using ::testing::InvokeWithoutArgs; using namespace testing; //Src code namespace using namespace std; 5.2 不要提交在test case中加入额外调试的打印，如果要提交，请注释起来5.3 让测试名称testname更容易理解概括了在特定上下文中系统表现出的行为，通过名字就能大概理解这个case的主要目的 should+result+if/when+action(if/when+action 有时可省略) 5.4 在fixture中, 可以把通用的构造条件写成createXXXX()函数，期望的条件exceptXXXX()函数5.5 一个xxxxunittest.cpp 对应一个xxxx.cpp scr文件 5.6 xxxx.cpp 对应的xxxxMock.h需要放到Mock文件夹中 5.6更多的规范可以参考: https://github.com/google/googletest/blob/master/docs/advanced.md5.Other topic solution抛异常的代码怎么处理无法使用Mock的场景 2.1 如何跑全局的函数，无法mock只能构造真实对象调用其真实代码 2.2 宏函数，无法mock只能构造真实对象调用其真实代码 2.3 非Virtual public函数 创建真实对象去调用/改为virtual/函数打桩/使用虚函数封装/使用template 静态多态
一、代码test的程序主要是将1-3号线程，绑定在1-3核上#include <stdlib.h> #include <stdio.h> // #include <sys/types.h> // #include <sys/sysinfo.h> #include <unistd.h> #define __USE_GUN // #include <sched.h> // #include <ctype.h> #include <string.h> #include <pthread.h> #define THREAD_MAX_NUM 100// 1个cpu内最多进程数 int num=0; //cpu中核数 void* threadFun(void* arg)// arg传递线程标号，自己定义 { //sleep(2); cpu_set_t mask;// cpu核的集合 cpu_set_t get;// 获取在集合中的cpu int* a=(int*)arg; printf("this is: %d\n",*a);// 打印这是第几个线程 CPU_ZERO(&mask);// 将集合置为空集 CPU_SET(*a,&mask);// 设置亲和力值 if(sched_setaffinity(0,sizeof(cpu_set_t),&mask)==-1)// 设置线程cpu亲和力 { printf("warning: could not set CPU affinity, continuing...\n"); } while(1) { CPU_ZERO(&get); if(sched_getaffinity(0,sizeof(get),&get)==-1)// 获取线程cpu亲和力 { printf("warning: could not get thread affinity, continuing...\n"); } for(int i=0;i<num;++i) { if(CPU_ISSET(i,&get))// 判断线程与哪个cpu有亲和力 { //printf("this thread %d is running processor: %d\n",i,i); int a=1000000*1000000; } } } return NULL; } int main(void) { num=sysconf(_SC_NPROCESSORS_CONF);// 获取核数 pthread_t thread[THREAD_MAX_NUM]; printf("system has %i processor(s).\n",num); int tid[THREAD_MAX_NUM]; for(int i=1;i<4;++i) { //if(i%2==0) //{ tid[i]=i;// 每个线程必须有个tid[i] pthread_create(&thread[i],NULL,threadFun,(void*)&tid[i]); //} //sleep(2); } for(int i=1;i<4;++i) { pthread_join(thread[i],NULL); } return 0; }1、代码功能概述主要功能是 创建多个线程，并将每个线程绑定到指定的 CPU 核上运行，同时通过循环检查线程的 CPU 亲和性（Affinity）。具体步骤如下：获取 CPU 核数：通过 sysconf(_SC_NPROCESSORS_CONF) 获取系统 CPU 核数。创建线程：创建 3 个线程（i=1,2,3），每个线程执行 threadFun 函数。设置 CPU 亲和性：在子线程中，将线程绑定到指定的 CPU 核（通过 sched_setaffinity）。持续检查亲和性：子线程进入无限循环，反复获取并验证其 CPU 亲和性。2、代码逐段解析2.1. 头文件与宏定义#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <pthread.h> #define THREAD_MAX_NUM 100 int num=0; // CPU 核数关键库：pthread.h（线程操作）、sched.h（调度相关函数）。宏定义：THREAD_MAX_NUM 定义最大线程数，num 存储 CPU 核数。2.2. 线程函数 threadFun*关键操作：设置亲和性：通过 sched_setaffinity 将线程绑定到 a 指定的 CPU 核。检查亲和性：通过 sched_getaffinity 获取当前线程的 CPU 亲和性，并验证是否生效。问题点：参数传递风险：tid[i] 是栈上数组，线程启动后可能被覆盖，导致参数错误。无限循环：while(1) 会导致线程无法退出，且占用 CPU 资源。无效计算：int a=1000000 * 1000000; 是无效代码，可能为占位符。2.3. 主函数 main*int main() { num = sysconf(_SC_NPROCESSORS_CONF); printf("system has %i processor(s).\n", num); pthread_t thread[THREAD_MAX_NUM]; int tid[THREAD_MAX_NUM]; for (int i=1; i<4; ++i) { tid[i] = i; pthread_create(&thread[i], NULL, threadFun, (void*)&tid[i]); } for (int i=1; i<4; ++i) { pthread_join(thread[i], NULL); } return 0; }关键操作：获取 CPU 核数：sysconf(_SC_NPROCESSORS_CONF)。创建线程：循环创建 3 个线程，传递线程编号 i 作为参数。问题点：线程参数风险：tid 数组在主线程中定义，子线程可能读取到错误值（如主线程修改 tid 后）。未处理错误：pthread_create 和 sched_setaffinity 的返回值未充分检查。3、代码存在的问题CPU 亲和性设置错误函数误用：sched_setaffinity 的第一个参数应为线程的 PID，但代码中传入 0（表示当前进程），而非线程 ID。正确做法是使用 pthread_setaffinity_np(pthread_self(), ...) 。示例修正：cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(*a, &mask); pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask);线程参数传递风险数据竞争：tid 数组在主线程中定义，子线程启动后可能被主线程修改。应改用动态内存分配或分离参数传递：int* tid = malloc(sizeof(int)); *tid = i; pthread_create(&thread[i], NULL, threadFun, (void*)tid);无限循环与资源浪费性能问题：while(1) 导致线程无法退出，且 CPU_ISSET 检查占用 CPU 资源。建议添加退出条件或休眠：while (1) { sched_getaffinity(...); sleep(1); // 降低 CPU 占用 }未处理函数返回值错误忽略：pthread_create 和 sched_setaffinity 的返回值未检查，可能导致程序崩溃或行为异常。总结核心功能：代码尝试将线程绑定到指定 CPU 核，但存在参数传递、函数误用和资源泄漏问题。优化方向：使用 pthread_setaffinity_np、改进参数传递、添加退出机制和降低 CPU 占用。二、测试流程taskset -c 0 ./test -c命令就是直接将主线程绑定在核0上
使用关键字快速定位日志查看问题一、什么是内存泄漏 内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失，而是应用程序分配某段内存后，由于设计错误，导致在释放该段内存之前就失去了对该段内存的控制，从而造成了内存的浪费。二、ASan推荐新的一个排查内存泄漏的工具：AddressSanitizer(ASan)，该工具为gcc自带，4.8以上版本都可以使用，支持Linux、OS、Android等多种平台，不止可以检测内存泄漏，它其实是一个内存错误检测工具，可以检测的问题有：内存泄漏堆栈和全局内存越界访问free后继续使用局部内存被外层使用Initialization order bugs具体使用也可以参考官方文档：https://github.com/google/sanitizers/wiki/AddressSanitizerAddressSanitizer (aka ASan) is a memory error detector for C/C++. It finds: Use after free (dangling pointer dereference) Heap buffer overflow Stack buffer overflow Global buffer overflow Use after return Use after scope Initialization order bugs Memory leaks2.1、Memory leaks - 内存泄漏只有跑到了那段代码才会把内存泄漏打印出来#include <iostream> #include <vector> #include <string> class LeakyClass { public: LeakyClass(size_t size) { data = new int[size]; // 动态分配内存 this->size = size; std::cout << "LeakyClass created with size " << size << std::endl; } void fillData() { for (size_t i = 0; i < size; ++i) { data[i] = static_cast<int>(i); } } void printData() { for (size_t i = 0; i < size; ++i) { std::cout << data[i] << " "; } std::cout << std::endl; } // 注意：没有析构函数释放data，导致内存泄漏 private: int* data; size_t size; }; void createLeak() { LeakyClass* leak1 = new LeakyClass(1000); leak1->fillData(); leak1->printData(); // 没有delete，内存泄漏 std::vector<LeakyClass*> leaks; for (int i = 0; i < 5; ++i) { LeakyClass* leak = new LeakyClass(500 * (i + 1)); leak->fillData(); leaks.push_back(leak); } // 这些对象也没有delete，造成多处内存泄漏 } int main() { createLeak(); std::cout << "Program finished.\\n"; return 0; }执行完之后打印信息：peter@iZbp1est4y0mdt6ask7pgpZ:~/memleak$ g++ -fsanitize=address -g memleak.cpp -o memleak && ./memleak LeakyClass created with size 1000 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 LeakyClass created with size 500 LeakyClass created with size 1000 LeakyClass created with size 1500 LeakyClass created with size 2000 LeakyClass created with size 2500 Program finished. ================================================================= ==330738==ERROR: LeakSanitizer: detected memory leaks Direct leak of 80 byte(s) in 5 object(s) allocated from: #0 0x7f1eb688c947 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10f947) #1 0x555fab85564f in createLeak() /home/peter/memleak/memleak.cpp:41 #2 0x555fab8557cb in main /home/peter/memleak/memleak.cpp:49 #3 0x7f1eb63b3082 in __libc_start_main ../csu/libc-start.c:308 Direct leak of 16 byte(s) in 1 object(s) allocated from: #0 0x7f1eb688c947 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10f947) #1 0x555fab8555dc in createLeak() /home/peter/memleak/memleak.cpp:34 #2 0x555fab8557cb in main /home/peter/memleak/memleak.cpp:49 #3 0x7f1eb63b3082 in __libc_start_main ../csu/libc-start.c:308 Indirect leak of 30000 byte(s) in 5 object(s) allocated from: #0 0x7f1eb688cb47 in operator new[](unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10fb47) #1 0x555fab8558e6 in LeakyClass::LeakyClass(unsigned long) /home/peter/memleak/memleak.cpp:8 #2 0x555fab85566e in createLeak() /home/peter/memleak/memleak.cpp:41 #3 0x555fab8557cb in main /home/peter/memleak/memleak.cpp:49 #4 0x7f1eb63b3082 in __libc_start_main ../csu/libc-start.c:308 Indirect leak of 4000 byte(s) in 1 object(s) allocated from: #0 0x7f1eb688cb47 in operator new[](unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10fb47) #1 0x555fab8558e6 in LeakyClass::LeakyClass(unsigned long) /home/peter/memleak/memleak.cpp:8 #2 0x555fab8555ec in createLeak() /home/peter/memleak/memleak.cpp:34 #3 0x555fab8557cb in main /home/peter/memleak/memleak.cpp:49 #4 0x7f1eb63b3082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: 34096 byte(s) leaked in 12 allocation(s). 编译方式很简单，只需要添加-fsanitize=address -g就可以检测出具体产生内存泄漏的位置以及泄漏空间的大小。2.2、Use after free - free后被使用#include <iostream> int main() { int *array = new int[100]; delete[] array; int a = array[0]; // error 第六行 return 0; }peter@iZbp1est4y0mdt6ask7pgpZ:~/memleak$ g++ -fsanitize=address -g useafterfree.cpp -o useafterfree && ./useafterfree ================================================================= ==330908==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000040 at pc 0x55dee3d402ed bp 0x7fff8d600f00 sp 0x7fff8d600ef0 READ of size 4 at 0x614000000040 thread T0 #0 0x55dee3d402ec in main /home/peter/memleak/useafterfree.cpp:6 #1 0x7f32d9c77082 in __libc_start_main ../csu/libc-start.c:308 #2 0x55dee3d401cd in _start (/home/peter/memleak/useafterfree+0x11cd) 0x614000000040 is located 0 bytes inside of 400-byte region [0x614000000040,0x6140000001d0) freed by thread T0 here: #0 0x7f32da136aaf in operator delete[](void*) (/lib/x86_64-linux-gnu/libasan.so.5+0x110aaf) #1 0x55dee3d402b5 in main /home/peter/memleak/useafterfree.cpp:5 #2 0x7f32d9c77082 in __libc_start_main ../csu/libc-start.c:308 previously allocated by thread T0 here: #0 0x7f32da135b47 in operator new[](unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10fb47) #1 0x55dee3d4029e in main /home/peter/memleak/useafterfree.cpp:4 #2 0x7f32d9c77082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: heap-use-after-free /home/peter/memleak/useafterfree.cpp:6 in main Shadow bytes around the buggy address: 0x0c287fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c287fff8000: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd 0x0c287fff8010: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c287fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c287fff8030: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa 0x0c287fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==330908==ABORTING 提示第六行代码有问题：0 0x55dee3d402ec in main /home/peter/memleak/useafterfree.cpp:62.3、Heap buffer overflow使用堆溢出，会导致coredump #include <iostream> int main() { int *array = new int[100]; array[0] = 0; int res = array[100]; // out of bounds 第七行 delete[] array; return res; }peter@iZbp1est4y0mdt6ask7pgpZ:~/memleak$ g++ -fsanitize=address -g headbufferoverflow.cpp -o headbufferoverflow && ./headbufferoverflow ================================================================= ==331087==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6140000001d0 at pc 0x55dda88d6341 bp 0x7ffd89871a30 sp 0x7ffd89871a20 READ of size 4 at 0x6140000001d0 thread T0 #0 0x55dda88d6340 in main /home/peter/memleak/headbufferoverflow.cpp:7 #1 0x7f6d012a1082 in __libc_start_main ../csu/libc-start.c:308 #2 0x55dda88d61ed in _start (/home/peter/memleak/headbufferoverflow+0x11ed) 0x6140000001d0 is located 0 bytes to the right of 400-byte region [0x614000000040,0x6140000001d0) allocated by thread T0 here: #0 0x7f6d0175fb47 in operator new[](unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10fb47) #1 0x55dda88d62be in main /home/peter/memleak/headbufferoverflow.cpp:5 #2 0x7f6d012a1082 in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: heap-buffer-overflow /home/peter/memleak/headbufferoverflow.cpp:7 in main Shadow bytes around the buggy address: 0x0c287fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff8000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c287fff8010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff8020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c287fff8030: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa 0x0c287fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==331087==ABORTING 2.4、Use after scope#include <iostream> int *p = 0; int main() { { int x = 0; p = &x; } *p = 5; // 第十行 return 0; }peter@iZbp1est4y0mdt6ask7pgpZ:~/memleak$ g++ -fsanitize=address -g useafterscope.cpp -o useafterscope && ./useafterscope ================================================================= ==331169==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffd335655b0 at pc 0x55dff79653a1 bp 0x7ffd33565580 sp 0x7ffd33565570 WRITE of size 4 at 0x7ffd335655b0 thread T0 #0 0x55dff79653a0 in main /home/peter/memleak/useafterscope.cpp:10 #1 0x7f7bdfbd5082 in __libc_start_main ../csu/libc-start.c:308 #2 0x55dff79651cd in _start (/home/peter/memleak/useafterscope+0x11cd) Address 0x7ffd335655b0 is located in stack of thread T0 at offset 32 in frame #0 0x55dff7965298 in main /home/peter/memleak/useafterscope.cpp:5 This frame has 1 object(s): [32, 36) 'x' (line 7) <== Memory access at offset 32 is inside this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-use-after-scope /home/peter/memleak/useafterscope.cpp:10 in main Shadow bytes around the buggy address: 0x1000266a4a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4a70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4aa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x1000266a4ab0: 00 00 f1 f1 f1 f1[f8]f3 f3 f3 00 00 00 00 00 00 0x1000266a4ac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4ad0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4ae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4af0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000266a4b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==331169==ABORTING 1
一、死锁demo#include <iostream> // 用于输入输出 #include <thread> // 用于线程创建和管理 #include <mutex> // 用于互斥锁 #include <chrono> // 用于时间操作 using std::cout; // 简化cout的调用 using std::endl; // 简化endl的调用 // 定义三个互斥锁 std::mutex mutex1; std::mutex mutex2; std::mutex mutex3; // 线程A：先锁mutex1，再锁mutex2（可能导致死锁） void FuncA() { std::cout << "FuncA Begin." << endl; std::lock_guard<std::mutex> guard1(mutex1); std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作 std::lock_guard<std::mutex> guard2(mutex2); std::cout << "FuncA completed." << endl; } // 线程B：先锁mutex2，再锁mutex3（可能导致死锁） void FuncB() { std::cout << "FuncB Begin." << endl; std::lock_guard<std::mutex> guard2(mutex2); std::this_thread::sleep_for(std::chrono::seconds(1)); std::lock_guard<std::mutex> guard3(mutex3); std::cout << "FuncB completed." << endl; } // 线程C：先锁mutex3，再锁mutex1（可能导致死锁） void FuncC() { std::cout << "FuncC Begin." << endl; std::lock_guard<std::mutex> guard3(mutex3); std::this_thread::sleep_for(std::chrono::seconds(1)); std::lock_guard<std::mutex> guard1(mutex1); std::cout << "FuncC completed." << endl; } int main() { // 创建三个线程 std::thread t1(FuncA); std::thread t2(FuncB); std::thread t3(FuncC); // 等待所有线程完成（实际可能因死锁卡死） t1.join(); t2.join(); t3.join(); cout << "All threads finished." << endl; return 0; }g++ --std=c++11 -g -O0 deadlock.cpp -o deadlock -lpthread查看运行状态并生成coredump文件：root@iZbp1est4y0mdt6ask7pgpZ:~# ps -aux | grep dead peter 329370 0.0 0.0 96148 1552 pts/2 Sl+ 13:55 0:00 ./deadlock root 329375 0.0 0.0 8872 656 pts/3 S+ 13:55 0:00 grep --color=auto dead root@iZbp1est4y0mdt6ask7pgpZ:~# kill -3 329370 root@iZbp1est4y0mdt6ask7pgpZ:~# **S：进程处于 休眠状态**（Interruptible Sleep），等待资源或事件触发。 **l：进程是 多线程的**（使用 clone() 系统调用创建线程）。 **+：进程是 前台进程组** 的成员（可通过 Ctrl+C 终止）。 触发产生coredump文件： peter@iZbp1est4y0mdt6ask7pgpZ:~/deadlock$ ./deadlock FuncA Begin. FuncB Begin. FuncC Begin. Quit (core dumped) 二、使用gdb来检测死锁1、info threads查看当前进程中所有线程的信息，也可以查看到部分堆栈信息(gdb) info thread Id Target Id Frame * 1 Thread 0x7f74738e0740 (LWP 329370) __pthread_clockjoin_ex (threadid=140138131617536, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145 2 Thread 0x7f74730de700 (LWP 329372) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1e0 <mutex3>, private=0) at lowlevellock.c:52 3 Thread 0x7f74728dd700 (LWP 329373) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb160 <mutex1>, private=0) at lowlevellock.c:52 4 Thread 0x7f74738df700 (LWP 329371) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 1 是主线程， 2-3是其他线程，而且都是加锁的过程，如果在生产环境中可能有很多线程在等锁，等什么锁2、查看每个线程在等什么锁(gdb) thread 2 [Switching to thread 2 (Thread 0x7f74730de700 (LWP 329372))] #0 **__lll_lock_wait** (futex=futex@entry=0x55f8c2bbb1e0 <**mutex3**>, private=0) at lowlevellock.c:52 52 lowlevellock.c: No such file or directory. (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1e0 <mutex3>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb1e0 <mutex3>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb1e0 <mutex3>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1e0 <mutex3>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74730dddf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb6519 in FuncB () at deadlock.cpp:28 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4797008: 0x55f8c2bb6489 <**FuncB()**>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4797008: 0x55f8c2bb6489 <**FuncB()**>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4797008) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4797008) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4797000) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 线程2【B】在给mutex3加锁，在等mutex3(gdb) thread 3 [Switching to thread 3 (Thread 0x7f74728dd700 (LWP 329373))] #0 **__lll_lock_wait** (futex=futex@entry=0x55f8c2bbb160 <**mutex1**>, private=0) at lowlevellock.c:52 52 in lowlevellock.c (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb160 <mutex1>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb160 <mutex1>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb160 <mutex1>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb160 <mutex1>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74728dcdf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb663a in FuncC () at deadlock.cpp:37 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4797158: 0x55f8c2bb65aa <FuncC()>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4797158: 0x55f8c2bb65aa <**FuncC()**>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4797158) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4797158) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4797150) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 线程3【**C】**在给mutex1加锁，在等mutex1(gdb) thread 4 [Switching to thread 4 (Thread 0x7f74738df700 (LWP 329371))] #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 52 lowlevellock.c: No such file or directory. (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb1a0 <mutex2>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb1a0 <mutex2>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1a0 <mutex2>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74738dedf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb63f8 in FuncA () at deadlock.cpp:19 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4796eb8: 0x55f8c2bb6368 <FuncA()>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4796eb8: 0x55f8c2bb6368 <**FuncA()**>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4796eb8) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4796eb8) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4796eb0) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 线程4【A】在给mutex2加锁，在等mutex23、查看这些在等的锁是谁拥有的(gdb) thread 2 [Switching to thread 2 (Thread 0x7f74730de700 (LWP 329372))] #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1e0 <mutex3>, private=0) at lowlevellock.c:52 52 in lowlevellock.c (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1e0 <mutex3>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb1e0 <mutex3>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb1e0 <mutex3>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1e0 <mutex3>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74730dddf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb6519 in FuncB () at deadlock.cpp:28 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4797008: 0x55f8c2bb6489 <FuncB()>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4797008: 0x55f8c2bb6489 <FuncB()>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4797008) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4797008) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4797000) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb) f 3 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1e0 <mutex3>) at /usr/include/c++/9/bits/std_mutex.h:100 100 int __e = __gthread_mutex_lock(&_M_mutex); (gdb) p mutex3 $1 = { <std::__mutex_base> = { _M_mutex = { __data = { __lock = 2, __count = 0, __owner = 329373, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = "\\002\\000\\000\\000\\000\\000\\000\\000\\235\\006\\005\\000\\001", '\\000' <repeats 26 times>, __align = 2 } }, <No data fields>} 进入到std::mutex::lock 这行代码所在的栈帧， 查看锁是被谁占有的，\_\_owner = 329373, 后面如果非零，就代表这个锁被这个线程占有着。(gdb) info thread Id Target Id Frame 1 Thread 0x7f74738e0740 (LWP 329370) __pthread_clockjoin_ex (threadid=140138131617536, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145 * 2 Thread 0x7f74730de700 (LWP 329372) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1e0 <mutex3>, private=0) at lowlevellock.c:52 3 Thread 0x7f74728dd700 (LWP **329373**) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb160 <mutex1>, private=0) at lowlevellock.c:52 4 Thread 0x7f74738df700 (LWP 329371) __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 查看线程号，发现这把锁是被线程3 329373拿到的这个线程2在等mutex3，而mutex3是被329373拿到的，也就是线程3下一步干嘛？ 应该去排查329373这个线程在干嘛3.1、线程329373的状态(gdb) thread 3 [Switching to thread 3 (Thread 0x7f74728dd700 (LWP 329373))] #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb160 <mutex1>, private=0) at lowlevellock.c:52 52 lowlevellock.c: No such file or directory. (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb160 <mutex1>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb160 <mutex1>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb160 <mutex1>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb160 <mutex1>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74728dcdf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb663a in FuncC () at deadlock.cpp:37 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4797158: 0x55f8c2bb65aa <FuncC()>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4797158: 0x55f8c2bb65aa <FuncC()>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4797158) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4797158) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4797150) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb) f 3 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb160 <mutex1>) at /usr/include/c++/9/bits/std_mutex.h:100 100 int __e = __gthread_mutex_lock(&_M_mutex); (gdb) p mutex1 $2 = { <std::__mutex_base> = { _M_mutex = { __data = { __lock = 2, __count = 0, __owner = 329371, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = "\\002\\000\\000\\000\\000\\000\\000\\000\\233\\006\\005\\000\\001", '\\000' <repeats 26 times>, __align = 2 } }, <No data fields>} 这个线程3在等mutex1，而mutex1是被329371拿到的，也就是线程43.2、线程329371的状态(gdb) thread 4 [Switching to thread 4 (Thread 0x7f74738df700 (LWP 329371))] #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 52 lowlevellock.c: No such file or directory. (gdb) bt #0 __lll_lock_wait (futex=futex@entry=0x55f8c2bbb1a0 <mutex2>, private=0) at lowlevellock.c:52 #1 0x00007f7473e2d0a3 in __GI___pthread_mutex_lock (mutex=0x55f8c2bbb1a0 <mutex2>) at ../nptl/pthread_mutex_lock.c:80 #2 0x000055f8c2bb6888 in __gthread_mutex_lock (__mutex=0x55f8c2bbb1a0 <mutex2>) at /usr/include/x86_64-linux-gnu/c++/9/bits/gthr-default.h:749 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1a0 <mutex2>) at /usr/include/c++/9/bits/std_mutex.h:100 #4 0x000055f8c2bb6a78 in std::lock_guard<std::mutex>::lock_guard (this=0x7f74738dedf0, __m=...) at /usr/include/c++/9/bits/std_mutex.h:159 #5 0x000055f8c2bb63f8 in FuncA () at deadlock.cpp:19 #6 0x000055f8c2bb7658 in std::__invoke_impl<void, void (*)()> (__f=@0x55f8c4796eb8: 0x55f8c2bb6368 <FuncA()>) at /usr/include/c++/9/bits/invoke.h:60 #7 0x000055f8c2bb75f0 in std::__invoke<void (*)()> (__fn=@0x55f8c4796eb8: 0x55f8c2bb6368 <**FuncA()**>) at /usr/include/c++/9/bits/invoke.h:95 #8 0x000055f8c2bb7582 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x55f8c4796eb8) at /usr/include/c++/9/thread:244 #9 0x000055f8c2bb753f in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x55f8c4796eb8) at /usr/include/c++/9/thread:251 #10 0x000055f8c2bb7510 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x55f8c4796eb0) at /usr/include/c++/9/thread:195 #11 0x00007f7473d17cb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #12 0x00007f7473e2a609 in start_thread (arg=<optimized out>) at pthread_create.c:477 #13 0x00007f7473b53353 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb) f 3 #3 0x000055f8c2bb69dc in std::mutex::lock (this=0x55f8c2bbb1a0 <mutex2>) at /usr/include/c++/9/bits/std_mutex.h:100 100 int __e = __gthread_mutex_lock(&_M_mutex); (gdb) p mutex2 $3 = { <std::__mutex_base> = { _M_mutex = { __data = { __lock = 2, __count = 0, __owner = 329372, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = "\\002\\000\\000\\000\\000\\000\\000\\000\\234\\006\\005\\000\\001", '\\000' <repeats 26 times>, __align = 2 } }, <No data fields>} 这个线程4在等mutex2，而mutex2是被329372拿到的，也就是线程23.3、综上所述这个线程2在等mutex3，而mutex3是被329373拿到的，也就是线程3 这个线程3在等mutex1，而mutex1是被329371拿到的，也就是线程4 这个线程4在等mutex2，而mutex2是被329372拿到的，也就是线程2 其中线程2 3 4分别对应thread B C A由此得出结论，出现了环形死锁的情况：还是拿上面图举例：线程A已经持有mutex1，想要申请mutex2，拿到mutex2后才可以释放mutex1和mutex2，而此时mutex2被线程B占用。线程B已经持有mutex2，想要申请mutex3，拿到mutex3后才可以释放mutex2和mutex3，而此时mutex3被线程C占用。线程C已经持有mutex3，想要申请mutex1，拿到mutex1后才可以释放mutex3和mutex1，而此时mutex1被线程A占用。如图，三个线程形成了一个环，死锁检测就是检查线程之间是否有环的存在。A->B, B->C, C->A
一、c++11 thread源码分析class thread { // class for observing and managing threads public: //.... template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0> explicit thread(_Fn&& _Fx, _Args&&... _Ax) { using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>; auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{}); _Thr._Hnd = reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id)); } };线程的函数传递是基于模板函数，任意类型参数都传入，并对参数做一份拷贝。回调函数完成之后，会进行释放。二、线程函数入口如何传递参数1、普通类型传递#include <iostream> #include <thread> using namespace std; using namespace chrono; using namespace this_thread; void ThreadMain(int p1, float p2, string str) { this_thread::sleep_for(chrono::milliseconds(100)); cout << "ThreadMain" << p1 << " "<< p2 << str << endl; } int main() { thread th; { float f1 = 12.1f;//f1释放掉是否会有影响 //所有的参数做赋值 th = thread(ThreadMain, 101, f1, "test string para"); } th.join(); return 0; } $ ./2_3 ThreadMain101 12.1test string parafloat 和 string类型都是可以正常传递。因为线程thread里面会做一份拷贝2、对象作为类型传递#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;}//这里必须加const 否则会编译不过 ~Para(){cout << "descontruct Para"<<endl;} string name; }; void ThreadMain(int p1, float p2, string str, Para p4) { this_thread::sleep_for(chrono::milliseconds(100)); cout << "ThreadMain" << p1 << " "<< p2 << str << p4.name<<endl; } int main() { thread th; { float f1 = 12.1f;//f1释放掉是否会有影响 Para p; p.name = "test Para class"; //所有的参数做赋值 th = thread(ThreadMain, 101, f1, "test string para", p); } th.join(); return 0; } $ g++ 2_3.cpp -o 2_3 -std=c++11 -lpthread $ ./2_3 construct Para Copy Para Copy Para descontruct Para descontruct Para Copy Para ThreadMain101 12.1test string para descontruct Para descontruct Para一次构造+3次拷贝构造+4次析构应该考虑使用引用的方式来传递。question:如果是引用的话，变量以及被释放掉了是否会有影响？3、指针作为类型传递#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;}//这里必须加const 否则会编译不过 ~Para(){cout << "descontruct Para"<<endl;} string name; }; // 线程入口函数 void ThreadMain1(Para* pstr) { this_thread::sleep_for(chrono::milliseconds(100)); //str 已经销毁 cout << "ThreadMainPtr name = " << pstr->name << endl; } int main() { //传递线程指针 thread th; { Para p; p.name = "test ThreadMain1 name"; //所有的参数做赋值 thread th(ThreadMain1, &p); th.join();//在这个作用域里面等，所以不会存在变量提前释放的情况 } //th.join(); return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/class_demo/dierzhang$ ./2_3 construct Para ThreadMainPtr name = test ThreadMain1 name descontruct Para没有产生构造函数。易错点#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;}//这里必须加const 否则会编译不过 ~Para(){cout << "descontruct Para"<<endl;} string name; }; // 线程入口函数 void ThreadMain1(Para* pstr) { this_thread::sleep_for(chrono::milliseconds(100)); //str 已经销毁 cout << "ThreadMainPtr name = " << pstr->name << endl; } int main() { //Para释放 不正常的版本 Para提前被释放了 { Para p; p.name = "test ThreadMain1 name"; //所有的参数做赋值 thread th1(ThreadMain1, &p); th1.detach(); } getchar();//这个场景要加这句话 防止主线程提前退出后，来不及执行子线程 return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/class_demo/dierzhang$ ./2_3 construct Para descontruct Para ThreadMainPtr name = ⚌⚌V name 1//自己输入的 以便让线程结束Para p; 已经释放，子线程再去访问已释放的资源。4、引用作为类型传递#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;}//这里必须加const 否则会编译不过 ~Para(){cout << "descontruct Para"<<endl;} string name; }; // 线程入口函数 void ThreadMainRef(Para& str) { this_thread::sleep_for(chrono::milliseconds(100)); cout << "ThreadMainRef name = " << str.name << endl; } int main() { //传递线程指针 正常的版本 { //引用传递 Para p; p.name = "test ref"; thread th(ThreadMainRef, ref(p)); th.join(); } return 0; }thread th(ThreadMainRef, ref(p)); 必须加上ref，否则会编译报错易错点#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;} ~Para(){cout << "descontruct Para"<<endl;} string name; }; // 线程入口函数 void ThreadMainRef(Para& str) { this_thread::sleep_for(chrono::milliseconds(100)); cout << "ThreadMainRef name = " << str.name << endl; } int main() { //Para释放 不正常的版本 Para提前被释放了 { //引用传递 Para p; p.name = "test ref"; thread th(ThreadMainRef, ref(p));//不加ref的话就会去找普通的传参，和模板有关 th.detach(); } getchar(); return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/class_demo/dierzhang$ ./2_3 construct Para descontruct Para ThreadMainRef name = test ref 1Para提前被释放了，但是这里依旧可以正常打印，原因就是？5、成员函数作为类型传递 #include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class MyThread { public: //线程入口函数 void Main() { cout << "MyThread Main " <<name<< endl; } string name; int age = 10; }; int main() { { MyThread myth; myth.name = "test mythread"; thread th(&MyThread::Main,&myth); th.join(); } return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/class_demo/dierzhang$ ./2_3 MyThread Main test mythread6、封装一个线程#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class Para { public: Para(){cout << "construct Para"<<endl;} Para(const Para&p ){cout << "Copy Para"<<endl;}//这里必须加const 否则会编译不过 ~Para(){cout << "descontruct Para"<<endl;} string name; }; // 线程入口函数 void ThreadMain1(Para* pstr) { this_thread::sleep_for(chrono::milliseconds(100)); //str 已经销毁 cout << "ThreadMainPtr name = " << pstr->name << endl; } // 线程入口函数 void ThreadMainRef(Para& str) { this_thread::sleep_for(chrono::milliseconds(1000)); //str 已经销毁 cout << "ThreadMainRef name = " << str.name << endl; } class MyThread { public: //线程入口函数 void Main() { cout << "MyThread Main " <<name<< endl; } string name; int age = 10; }; class IThread { public: virtual void Start() { isExit = false; th_ = std::thread(&IThread::Main, this); } virtual void Stop() { isExit = true; Wait(); } virtual void Wait() { if(th_.joinable())//当可以join的时候才join th_.join(); } bool isExitStatus() { return isExit; } private: virtual void Main() = 0; bool isExit = false; std::thread th_; }; class Thread : public IThread { public: void Main() override { cout << "Thread::Main" <<endl; while(!isExitStatus()) { this_thread::sleep_for(chrono::milliseconds(100)); cout << "."<<flush;//要刷新，否则是先输出到缓冲区中 } cout << "Thread::Main end" <<endl; } string name; }; int main() { { //使用封装的线程 Thread testThread; testThread.name = "Thread name"; testThread.Start(); sleep_for(seconds(3)); testThread.Stop(); //testThread.Wait();//等待子线程退出 //getchar(); } getchar(); //th.join(); return 0; }代码执行好像有点问题7、lambda 临时函数作为线程入口函数7.1、lambda 线程示例:我们需要在每个线程上添加一个标记。鉴于我们使用lambda，所以我们可以尝试下它的捕获能力。通过将i的值传递给线程，使用[i]我们可以将索引传递到线程函数中：thread t([](int i) { cout << "Hello World from lambda thread."<<i << endl; },123);在此基础上我们将创建5个线程，然后把线程放进一个vector容器中， 用for\_each()完成线程的汇合(join):#include <iostream> #include <thread> #include <vector> #include <algorithm> int main() { // vector 容器存储线程 std::vector<std::thread> workers; for (int i = 0; i < 5; i++) { workers.push_back(std::thread([]() { std::cout << "thread function\\n"; })); } std::cout << "main thread\\n"; // 通过 for_each 循环每一个线程 // 第三个参数赋值一个task任务 // 符号'[]'会告诉编译器我们正在用一个匿名函数 // lambda函数将它的参数作为线程的引用t // 然后一个一个的join std::for_each(workers.begin(), workers.end(), [](std::thread &t) { t.join(); }); return 0; } 执行结果： thread function thread function thread function thread function thread function main thread可参考：https://www.jb51.net/article/155157.htm7.2、线程入口为类成员Lambda函数#include <iostream> #include <thread> #include <string> using namespace std; using namespace chrono; using namespace this_thread; class TestLambda { public: void Start() { thread t([this]() { cout << "TestLambda thread this."<<name << endl; }); t.join(); } string name = "test lambda"; }; int main() { //使用Lambda来处理 TestLambda th; th.Start(); return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/class_demo/dierzhang$ ./2_3 TestLambda thread this.test lambda7.3、call\_once 多线程调用函数，但函数只进入一次void SystemInit() { cout << "Call SystemInit" << endl; } void CallOnceSystemInit() { static std::once_flag flag; std::call_once(flag, SystemInit); }
为什么要使用多线程 如何查看gdb分析
一、使用多线程的场景任务分解：耗时的操作，任务分解，实时响应 数据分解：充分利用多核 CPU 处理数据 数据流分解：读写分离，解耦合设计二、C++ 11 多线程快速入门1. 先动手写第一个 c++11 多线程程序#include <iostream> #include <thread> using namespace std; using namespace chrono; using namespace this_thread; // 线程入口函数 void ThreadMain() { cout << " 进入子线程" << get_id() << endl; for (int i = 0; i < 10; i++) { cout << "in thread id = " << i << endl; sleep_for(seconds(1)); // 线程释放 cpu 资源 1 秒 } cout <<get_id()<< " 线程退出" << endl; } // 主线程入口 int main(int argc, char* argv[]) { cout << "主线程ID "<< get_id() << endl; //1 创建并启动子线程 ， 进入线程函数 thread th(ThreadMain); cout << "等待子线程退出" << endl; th.join(); cout << "等待主线程返回" << endl; return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/xiahoujun_class_demo/dierzhang$ g++ 2_1.cpp -o 2_1 -std=c++11 -lpthread peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/xiahoujun_class_demo/dierzhang$ ./2_1 主线程ID 140641257162560 等待子线程退出 进入子线程140641257158400 in thread id = 0 in thread id = 1 in thread id = 2 in thread id = 3 in thread id = 4 in thread id = 5 in thread id = 6 in thread id = 7 in thread id = 8 in thread id = 9 140641257158400 线程退出 等待主线程返回分析：如果是多核的情况下 cout << "等待子线程退出" << endl; 和 cout << " 进入子线程" << get\_id() << endl; 之间的打印可能存在交替使用sleep\_for(seconds(1)); 可以让资源得到释放th.join();等待子线程退出2. 测试生命周期代码// 线程入口函数 bool is_exit = false; void ThreadMain() { while(!is_exit) { cout<<"ThreadMain"<<endl; //this_thread::sleep_for(100ms); this_thread::sleep_for(chrono::milliseconds(100)); } } // 主线程入口 int main(int argc, char* argv[]) { //1 创建并启动子线程 ， 进入线程函数 { thread th(ThreadMain);//// 出错 线程句柄清理 ， 线程还在运行 } return 0; } peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/xiahoujun_class_demo/dierzhang$ g++ 2_2.cpp -o 2_2 -std=c++11 -lpthread peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/xiahoujun_class_demo/dierzhang$ ./2_2 terminate called without an active exception Aborted (core dumped)分析：thread th对象被销毁，但是子线程还在运行。打开core文件的堆栈peter@iZbp1aq6c9kbbder4q405mZ:~/test/threadTopic/xiahoujun_class_demo/dierzhang$ gdb 2_2 core GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from 2_2... (No debugging symbols found in 2_2) [New LWP 2290765] [New LWP 2290766] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Core was generated by `./2_2'. Program terminated with signal SIGABRT, Aborted. #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. [Current thread is 1 (Thread 0x7fd77e71d740 (LWP 2290765))] (gdb) bt #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 #1 0x00007fd77e896859 in __GI_abort () at abort.c:79 #2 0x00007fd77eb1c951 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #3 0x00007fd77eb2847c in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #4 0x00007fd77eb284e7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6 #5 0x00005593c815649d in std::thread::~thread() () #6 0x00005593c8156397 in main () (gdb) f 5 #5 0x00005593c815649d in std::thread::~thread() () (gdb) f 6 #6 0x00005593c8156397 in main () (gdb)可以查看thread::\~thread()内部源码看下怎么实现的 ~thread() { if (joinable()) std::terminate(); }解决办法：{ //解决办法1： 主线程等待子线程退出 缺点：但是这样的话主线程就啥事也做不了了 thread th(ThreadMain); th.join(); // 等待子线程 ， 主线程阻塞 } { //解决办法2：主线程和子线程同时运行，又不想维护th这个对象，那么可以让主线程和子线程分离 缺点：主线程退出后，子线程不一定退出 thread th(ThreadMain); th.detach(); // 子线程分离 }针对解决办法2，如果主线程退出后[全局空间以及栈上的空间释放掉了]，子线程没有退出的话，而且此时子线程有访问到主线程中释放额一些资源，就会导致程序异常coredump。比如实际场景中，当你关闭窗口的时候，又弹出了一个窗口。怎么解决解决办法2中遇到的问题，方案A、如果detach掉了一个子线程，这个子线程就不要再访问外部变量，只访问线程内部声明的变量方案B、主线程退出之后，通知下子线程，子线程自己去退出。方案B的demo：#include <iostream> #include <thread> using namespace std; using namespace chrono; using namespace this_thread; // 线程入口函数 bool is_exit = false; void ThreadMain() { while(!is_exit) { cout<<"ThreadMain"<<endl; //this_thread::sleep_for(100ms); this_thread::sleep_for(chrono::milliseconds(100)); } } // 主线程入口 int main(int argc, char* argv[]) { { //解决办法3： thread th(ThreadMain); this_thread::sleep_for(chrono::milliseconds(100)); is_exit = true; cout << "主线程阻塞，等待子线程退出"<< endl; th.join(); // 等待子线程 ， 主线程阻塞 cout << "子线程已经退出" <<endl; } return 0; }设置一个标志位，当主线程要退出时，通知子线程 is\_exit = true;
1.多线程的一个demo/* 使用多线程实现买票的案例。 有3个窗口，一共是100张票。 */ #include <stdio.h> #include <pthread.h> #include <unistd.h> // 全局变量，所有的线程都共享这一份资源。 int tickets = 100;//放到sellticket里面的话 就是每个线程卖100张门票！！ void * sellticket(void * arg) { // 卖票 tickets时临界资源 while后面的时临时区 while(tickets > 0) { usleep(6000);//单位微秒 printf("%ld 正在卖第 %d 张门票\\n", pthread_self(), tickets); tickets--;//线程并发去执行 抢CPU执行资格 } return NULL; } int main() { // 创建3个子线程 要执行的内容一样 就用同一个callback: sellticket //main线程啥也不干 只是负责对子线程的回收 pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, sellticket, NULL); pthread_create(&tid2, NULL, sellticket, NULL); pthread_create(&tid3, NULL, sellticket, NULL); // 回收子线程的资源,阻塞 线程没结束会阻塞在这 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); // 设置线程分离。 不使用pthread_join 使用pthread_detach也可以 // pthread_detach(tid1); // pthread_detach(tid2); // pthread_detach(tid3); pthread_exit(NULL); // 退出主线程 不影响子线程 return 0; } /* [haozheng@hzlinb30 practice]$ ./3_8_selltickets 140063953090304 正在卖第 100 张门票 140063944697600 正在卖第 100 张门票 140063936304896 正在卖第 98 张门票 140063953090304 正在卖第 97 张门票 140063944697600 正在卖第 97 张门票 140063936304896 正在卖第 95 张门票 140063944697600 正在卖第 3 张门票 140063936304896 正在卖第 2 张门票 140063953090304 正在卖第 1 张门票 140063944697600 正在卖第 0 张门票 140063936304896 正在卖第 -1 张门票 */分析：这里为啥要加上 usleep(6000);因为cpu执行的非常快，这里usleep就是让当前的线程休眠，这样好让出cpu。主要是为了演示效果一定要在主线程结束之前运行子线程，所以要加上join等待在打印sellticket函数的时候，会出现两种异常情况:case1：两个线程会出现卖同一张票的情况，printf("%ld 正在卖第 %d 张门票\n", pthread\_self(), tickets); //打印的都是100因为当有两个线程thread1和thread2同时进入到 while(tickets > 0) {之后，thread1刚好走到printf这一步之后，资源就被thread2拿走了，thread2在打印print之前，thread1始终都还没拿到锁case2:线程会出现卖负数票的情况，同case1一样，当printf为0的时候，刚好两个线程都在printf这一行，然后两个线程都去执行 - -这个操作，就会导致出现负数这种情况，也就是卖负数张票这种情况Note:为了测试等待，能否直接使用for循环而不使用join来进行等待 int i; for(i = 0; i < 1000000; i++) { ; // 延时 }子线程得到了运行，因为主线程需要的时间比较长，而时间片不够，所以其它线程就运行了，等其它线程执行完后，又返回了主线程继续进行执行，直至主线程结束。对int变量赋值的操作是原子的吗？ —> 不是对int变量赋值的操作是原子的吗？可以进行测试，当两个线程通知对一个共享变量进行+1的话，最终也许并不是加了2，而是只加了1多CPU如何进行调度sleep\_for函数的使用1）使用说明 std::this_thread::sleep_for函数是C11的休眠函数，表示当前线程休眠一段时间，休眠期间不与其他线程竞争CPU，根据线程需求，等待若干时间。 由于是一个跨平台的函数，因此在代码中大量应用，避免了在不同平台之间所以通过宏定义编译问题。在windows下，可以简单替代Sleep, 在Linux下，替代usleep 2）调用例子 头文件定义：#include <thread> std::this_thread::sleep_for(std::chrono::milliseconds(50));//睡眠50毫秒 参考： <https://blog.51cto.com/fengyuzaitu/2565089> // this_thread::sleep_for example #include <iostream> // std::cout, std::endl #include <thread> // std::this_thread::sleep_for #include <chrono> // std::chrono::seconds int main() { std::cout << "countdown:\\n"; for (int i=10; i>0; --i) { std::cout << i << std::endl; std::this_thread::sleep_for (std::chrono::seconds(1)); } std::cout << "Lift off!\\n"; return 0; }基础知识入门与总结多线程总结vedioSTL的容器多线程问题线程安全多线程访问文件导致的问题各种锁的使用枚举在多线程中的应用lamber表达式在多线程中的坑单核和多核执行下面这段代码区别条件变量C++ - 线程安全的std::cout
如果训练完成度的缺陷和reward的曲线高度差一致且单调递增，就说明算法正在每次迭代而学习和改进。至于训练完成度是否要高于reward或者低于reward其实并没多少影响 evalution 趋近于100的话就相对稳定横轴：时间纵轴：左边reward， 右边完成度蓝色：训练完成度training completion， 训练过程中的完成度，应该也会越来越高， 一般蓝色会低于红色，蓝色是不断尝试，看下这样或者那样跑好不好红色：评估完成度evaluating completion, 校验过程中的完成度， 最后能达到100% ，会找一个目前了解过的最好的策略，所以完成度会比较高。如果评估的接近100%的话，就说明小车可以完整的在这一个赛道上行驶起来。Note：如果出现过度拟合的情况的话，最后这根线会出现回落绿色：奖励值reward， reward function 随着完成度不断变高，如果奖励函数没问题的话，reward也会不断变高下面这种曲线是比较好的结果：如果蓝色和绿色的线是成正比的话且都是单调递增的话，就说明奖励和完成度有关，那么这个模型就比较好，我们可以让他继续训练，最终100如果平均奖励和赛道完成度总是显示趋于收敛的趋势，那训练任务就进展的非常顺利。特别是如果每个阶段，持续的进度都会达到接近100%并且奖励稳定，模型已经收敛，此时不需要进行训练，否则我们需要克隆模型，继续进行训练。但是如果看评估图表会发现一直在20%到30%就说明没有过发夹弯
std::lock 是一个 C++ 用于避免死锁的工具，用于一次性锁住多个互斥锁（std::mutex），它通过确保锁定顺序一致，避免了死锁的发生。死锁是指程序中有多个共享资源，通常情况下，多个共享资源需要多个互斥锁来保护，以确保线程在访问这些资源时不会发生冲突。如果两个或更多线程在相互等待对方释放锁，就会导致它们无法继续执行，最终造成程序停滞或卡死。一、死锁代码#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex mutex1; std::mutex mutex2; void thread1_func() { std::lock_guard<std::mutex> lock1(mutex1); std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::lock_guard<std::mutex> lock2(mutex2); std::cout << "Thread 1 acquired both mutexes\\n"; } void thread2_func() { std::lock_guard<std::mutex> lock2(mutex2); std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::lock_guard<std::mutex> lock1(mutex1); std::cout << "Thread 2 acquired both mutexes\\n"; } int main() { std::thread t1(thread1_func); std::thread t2(thread2_func); t1.join(); t2.join(); return 0; }产生了死锁二、std::lock 解决死锁#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex mutex1; std::mutex mutex2; void thread1_func() { std::lock(mutex1, mutex2); std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock); std::cout << "Thread 1 acquired both mutexes\\n"; } void thread2_func() { std::lock(mutex1, mutex2); std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock); std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock); std::cout << "Thread 2 acquired both mutexes\\n"; } int main() { std::thread t1(thread1_func); std::thread t2(thread2_func); t1.join(); t2.join(); return 0; }std::lock(mutex1, mutex2); 的作用是同时锁定多个互斥锁，避免死锁的发生。它会阻塞直到所有指定的锁都被成功获取。但是，std::lock 本身并不管理锁的释放，需要程序员手动释放锁。为了确保锁在作用域结束时自动释放，必须使用 std::lock_guard 或其他锁管理类。这里使用 std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock); 和 std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock); 是告诉 lock_guard 这两个锁已经被锁定（由 std::lock 锁定），lock_guard 只负责在析构时释放它们。总结：std::lock 负责安全地同时锁定多个互斥锁，std::lock_guard 负责自动释放锁，二者结合使用可以避免死锁并保证锁的正确释放。三、源码实现template <class _Lock0, class _Lock1> bool _Lock_attempt_small(_Lock0& _Lk0, _Lock1& _Lk1) { _Lk0.lock(); { _Unlock_one_guard<_Lock0> _Guard{_Lk0}; if (_Lk1.try_lock()) { _Guard._Lk_ptr = nullptr; return false; } } _STD this_thread::yield(); return true; } template <class _Lock0, class _Lock1> void _Lock_nonmember1(_Lock0& _Lk0, _Lock1& _Lk1) { while (_Lock_attempt_small(_Lk0, _Lk1) && _Lock_attempt_small(_Lk1, _Lk0)) {} } _EXPORT_STD template <class _Lock0, class _Lock1, class... _LockN> void lock(_Lock0& _Lk0, _Lock1& _Lk1, _LockN&... _LkN) { _Lock_nonmember1(_Lk0, _Lk1, _LkN...); }\_Lock\_nonmember1 调用 \_Lock\_attempt\_small 函数尝试同时对多个锁进行加锁\_Lock\_attempt\_small 函数中：首先，对其中的任意一个锁进行加锁然后，尝试对另外一个锁进行加锁，如果成功，则返回 false。\_Lock\_nonmember1 函数中的 while 循环退出。继续执行当前线程后续代码如果加锁失败，则对第一个锁进行释放锁的操作。并且主动放弃剩余的时间片，等待操作系统再次调度执行当前线程，相当于等待一段时间，再重新尝试对两个锁进行加锁操作。