每天进步一点点
type: channel
link: https://www.petertechblog.cn/
description: Your description here.
items: 高性能的日志模块spdlog
items: 单元测试GtestGmock总结
items: 线程绑核用例
items: 利用Asan如何调试内存泄漏
items: 如何使用gdb来解决死锁
items: C++11 线程创建的多种方式和参数传递
items: 为什么要使用多线程 如何查看gdb分析
items: 多线程问题
items: reward graph 怎么分析
items: C++ std::lock 避免死锁工具

高性能的日志模块spdlog
type: item
date: 2025-07-14T21:37:00+08:00
description: 高性能的日志模块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::millisec…

单元测试GtestGmock总结
type: item
date: 2025-02-14T22:09:00+08:00
description: 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(b…

线程绑核用例
type: item
date: 2025-02-09T15:21:00+08:00
description: 一、代码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] 是栈上数…

利用Asan如何调试内存泄漏
type: item
date: 2025-02-02T16:39:00+08:00
description: 使用关键字快速定位日志查看问题一、什么是内存泄漏 内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失，而是应用程序分配某段内存后，由于设计错误，导致在释放该段内存之前就失去了对该段内存的控制，从而造成了内存的浪费。二、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…

如何使用gdb来解决死锁
type: item
date: 2025-02-01T15:01:00+08:00
description: 一、死锁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) __pthrea…

C++11 线程创建的多种方式和参数传递
type: item
date: 2025-01-25T22:24:00+08:00
description: 一、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 P…

为什么要使用多线程 如何查看gdb分析
type: item
date: 2025-01-13T19:51:00+08:00
description: 一、使用多线程的场景任务分解：耗时的操作，任务分解，实时响应 数据分解：充分利用多核 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…

多线程问题
type: item
date: 2025-01-12T19:30:00+08:00
description: 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::th…

reward graph 怎么分析
type: item
date: 2025-01-09T15:49:00+08:00
description: 如果训练完成度的缺陷和reward的曲线高度差一致且单调递增，就说明算法正在每次迭代而学习和改进。至于训练完成度是否要高于reward或者低于reward其实并没多少影响 evalution 趋近于100的话就相对稳定横轴：时间纵轴：左边reward， 右边完成度蓝色：训练完成度training completion， 训练过程中的完成度，应该也会越来越高， 一般蓝色会低于红色，蓝色是不断尝试，看下这样或者那样跑好不好红色：评估完成度evaluating completion, 校验过程中的完成度， 最后能达到100% ，会找一个目前了解过的最好的策略，所以完成度会比较高。如果评估的接近100%的话，就说明小车可以完整的在这一个赛道上行驶起来。Note：如果出现过度拟合的情况的话，最后这根线会出现回落绿色：奖励值reward， reward function 随着完成度不断变高，如果奖励函数没问题的话，reward也会不断变高下面这种曲线是比较好的结果：如果蓝色和绿色的线是成正比的话且都是单调递增的话，就说明奖励和完成度有关，那么这个模型就比较好，我们可以让他继续训练，最终100如果平均奖励和赛道完成度总是显示趋于收敛的趋势，那训练任务就进展的非常顺利。特别是如果每个阶段，持续的进度都会达到接近100%并且奖励稳定，模型已经收敛，此时不需要进行训练，否则我们需要克隆模型，继续进行训练。但是如果看评估图表会发现一直在20%到30%就说明没有过发夹弯

C++ std::lock 避免死锁工具
type: item
date: 2025-01-08T10:53:00+08:00
description: 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_s…