一、固定线程池——FixedThreadPool1没有输出func且显示核心转储线程池对象被销毁时池子里的 std::thread 线程对象还处于 joinable() 状态既没 join() 也没 detach()C 标准强制调用 std::terminate() 终止程序。日志里出现了 func说明线程池的任务函数执行了但线程没被正确回收。失败原因:将任务添加到线程池因为主线程的运行速度太快连执行的机会都没有就结束了改进做了休眠等待任务执行gdb调试:0 __GI_raise → 发出 SIGABRT 信号1 __GI_abort → 终止程序2 std::terminate() → C 强制终止因为线程没 join3 std::thread::~thread() → 线程对象析构时发现还没 join ...16 std::list::clear() → 你的线程组被清空了17 pool::FixedThreadPool::StopThreadGroup() → 你的 Stop 函数21 call_once → 确保 StopThreadGroup() 只执行一次27 Stop() → 析构里调用 Stop()28 ~FixedThreadPool() → 线程池析构函数31 main() → 程序入口pool::FixedThreadPool::StopThreadGroup() 就是StopThreadGroup 函数崩溃的直接原因就是在 StopThreadGroup 里调用了 list::clear()而此时线程还没被 join。std::this_thread::yield()主线程让出CPU会导致偶尔打印不出来问题根源yield 不是用来 “等待” 的std::this_thread::yield()的作用是让出当前的 CPU 时间片它只会让主线程 “暂停一小会儿”但不会等待任何条件完成。在这个场景里主线程调用yield()让出 CPU调度器可能马上又把 CPU 分给主线程也可能分给工作线程如果调度器又给了主线程主线程会立刻继续执行return 0程序直接退出工作线程还没来得及执行cout程序就结束了所以打印不出来yield()是 “让一下”不是 “等一下”所以它的等待效果是不可靠的、随机的这就是你偶尔能打印、偶尔不行的原因。ac:9999先打印出来了但最后一个任务func:9999才打印出来说明主线程提前执行了cout ac:但此时最后一个任务还没执行完2线程池的拒绝策略线程池满了自己处理任务3任务是无参无返回值的如何返回其他类型将任务加入到任务队列报错原因因为std::function是可以拷贝的所以要求内部包含的所有东西都是可以拷贝的但是std::packaged_task由于限制没有拷贝和赋值支持移动所以std::function中不能直接放std::packaged_tast,因此packaged_task放在堆上只创建一份用shared_ptr管理它shared_ptr是可以随便拷贝的成功获取返回值类型4#includelatch一直标红版本是C20也标红还不知道为什么5CPU利用率和运行效率对比两核不使用线程池一个CPU的利用率为100%使用线程池两个CPU的利用率为100%轻任务单线程 线程池 生成随机数、简单计算→ 线程池调度开销太大反而慢重任务线程池 单线程 排序、复杂计算、IO→ 线程池多核并行快几倍甚至十几倍16核核数越多效率越快6为什么使用多线程申请内存时比单线程申请内存的速度还要慢慢在什么地方为什么慢提前预留空间也并没有很大优化内存分配是天生的 “串行” 操作多线程争抢只会让它变慢。核心原因内存分配器malloc是 “带锁的”我们平时用的new/malloc底层都是调用操作系统的内存分配器最常见的是glibc的ptmalloc。为了保证线程安全它内部有一把全局锁。单线程场景线程直接调用malloc没有任何竞争也没有加锁、解锁的开销。分配器可以高效地找到合适的内存块直接返回。多线程场景多个线程同时调用malloc都要争抢这把锁。同一时间只有一个线程能拿到锁进行分配其他线程都必须阻塞等待。这种场景下多线程分配就变成了串行排队执行而且额外增加了大量的锁竞争、线程上下文切换开销。提前预留空间reserve为什么优化不大提前 reserve(m) 空间只是避免了内存拷贝但绕不开 malloc 本身的瓶颈。 reserve 只是一次性申请一块大内存避免了多次扩容的拷贝开销。 但在多线程下每个 vector 调用 reserve 时依然要去争抢全局锁所以锁竞争的问题还是存在。 当你一次性 reserve 大块内存时锁持有时间反而变长了其他线程等待的时间也更长了甚至可能比动态扩容更慢。7优化resize:创建并初始化把size设为 m创建 m 个元素[]访问100% 安全单线程和多线程同时用了resize预留空间但是使用的是自定义的随机生成函数且多线程用了门栓时间对比如下单线程和多线程同时用了resize预留空间以及#includerandom中自带的随机生成函数且多线程用了门栓时间对比如下reserve:看到的时间不准、是假的、是非法访问导致的开辟m 个空间size 依然是 0不创建任何元素正确编写后resize比reserve更快速单线程和多线程同时用了reserve预留空间但是使用的是自定义的随机生成函数且多线程用了门栓时间对比如下单线程和多线程同时用了reserve预留空间以及#includerandom中自带的随机生成函数且多线程用了门栓时间对比如下发现造成多线程生成数据耗时如此慢的主要原因是因为随机数生成,换成系统的随机函数更安全更高效8wait和wait_for9C 线程池的析构顺序与生命周期二、缓存线程池——CachedTreadPoolCPU占比不使用线程池时CPU占比CPU1处于100%使用缓存式线程池时,CPU占比最初提前用resize开辟了空间用了自定义的随机生成函数单线程和多线程同时提前用resize开辟了空间用了自定义的随机生成函数且多线程用了门栓时间对比如下第一次优化提前用rerserve开辟空间并且用了random库的随机生成单线程和多线程同时提前用reserve开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下第二次优化提前用resize开辟空间并且用了random库的随机生成单线程和多线程同时提前用resize开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下三、工作窃取线程池——WorkStealingPool1出现了死锁是因为在获取任务时使用了wait,导致了死锁2出现了无输出的情况Debugcmake -DCMAKE_BUILD_TYPEDebug ..make -j$(nproc)启动调试gdb ./threadpool3运行调试run发现卡住不动中断程序ctrlc查看所有线程info threads查看函数掉用栈thread 1bt#6 main()程序从main函数开始运行。#5 test2() at threadpoolTest3.cpp:97进入了test2()函数运行到第 97 行。#4 std::latch::wait()主线程在这里卡住了它在调用lat_steal.wait();#3 ~ #0 底层系统等待主线程已经被操作系统挂起不占 CPU不执行任何代码死等。主线程在 test2 函数的第 97 行也就是 lat_steal.wait() 处永远等不到任务完成。再查看一个非主线程的运行状态#0 futex_abstimed_wait_cancelable#1 pthread_cond_wait_common#2 pthread_cond_clockwait#3 std::condition_variable::wait_until#4 std::condition_variable::wait_for#5 SyncQueue::Task() at SyncQueue_4.hpp:98#6 WorkStealingPool::RunThread() at WorkStealingPool.hpp:50主线程卡死在latch.wait()→ 任务没执行完工作线程全部卡在SyncQueue::Task()→ 拿不到任务提交任务时用了默认队列容量 500而任务数是 10000 → 队列早就塞满了主线程在Put()时就已经阻塞后面的任务根本没提交进去队列里的任务数为 0 → 工作线程永远拿不到任务只能一直wait_for循环mypool() 队列太小装不下 10000 个任务 → 程序堵死卡死 → 无输出mypool(10000,16) 队列够大 → 能装下所有任务 → 正常跑 → 有输出单线程CPU利用率多线程CPU利用率最初提前用resize开辟了空间用了自定义的随机生成函数单线程和多线程同时提前用resize开辟了空间用了自定义的随机生成函数且多线程用了门栓时间对比如下第一次优化提前用rerserve开辟空间并且用了random库的随机生成单线程和多线程同时提前用reserve开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下第二次优化提前用resize开辟空间并且用了random库的随机生成单线程和多线程同时提前用resize开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下四、ScheduleThreadPoo——定时线程池1定时器出现了周期延迟TimerManage 的 loop 线程↓epoll_wait↓触发定时器 A → 执行回调↓触发定时器 B → 执行回调如果 func() 执行很慢后面的定时器必须等它执行完 周期就会越来越不准、越来越延迟定时器线程不阻塞Heavy task执行期间Fast task仍然每秒准时执行任务并发执行Fast task1和Fast task 2几乎同时执行时间差仅1ms周期性任务不受影响所有定时器都按预期间隔触发2使用map实现1个Timer管理N个Callback时间精度
C++11实现线程池:项目实现过程的报错与gdb调试
一、固定线程池——FixedThreadPool1没有输出func且显示核心转储线程池对象被销毁时池子里的 std::thread 线程对象还处于 joinable() 状态既没 join() 也没 detach()C 标准强制调用 std::terminate() 终止程序。日志里出现了 func说明线程池的任务函数执行了但线程没被正确回收。失败原因:将任务添加到线程池因为主线程的运行速度太快连执行的机会都没有就结束了改进做了休眠等待任务执行gdb调试:0 __GI_raise → 发出 SIGABRT 信号1 __GI_abort → 终止程序2 std::terminate() → C 强制终止因为线程没 join3 std::thread::~thread() → 线程对象析构时发现还没 join ...16 std::list::clear() → 你的线程组被清空了17 pool::FixedThreadPool::StopThreadGroup() → 你的 Stop 函数21 call_once → 确保 StopThreadGroup() 只执行一次27 Stop() → 析构里调用 Stop()28 ~FixedThreadPool() → 线程池析构函数31 main() → 程序入口pool::FixedThreadPool::StopThreadGroup() 就是StopThreadGroup 函数崩溃的直接原因就是在 StopThreadGroup 里调用了 list::clear()而此时线程还没被 join。std::this_thread::yield()主线程让出CPU会导致偶尔打印不出来问题根源yield 不是用来 “等待” 的std::this_thread::yield()的作用是让出当前的 CPU 时间片它只会让主线程 “暂停一小会儿”但不会等待任何条件完成。在这个场景里主线程调用yield()让出 CPU调度器可能马上又把 CPU 分给主线程也可能分给工作线程如果调度器又给了主线程主线程会立刻继续执行return 0程序直接退出工作线程还没来得及执行cout程序就结束了所以打印不出来yield()是 “让一下”不是 “等一下”所以它的等待效果是不可靠的、随机的这就是你偶尔能打印、偶尔不行的原因。ac:9999先打印出来了但最后一个任务func:9999才打印出来说明主线程提前执行了cout ac:但此时最后一个任务还没执行完2线程池的拒绝策略线程池满了自己处理任务3任务是无参无返回值的如何返回其他类型将任务加入到任务队列报错原因因为std::function是可以拷贝的所以要求内部包含的所有东西都是可以拷贝的但是std::packaged_task由于限制没有拷贝和赋值支持移动所以std::function中不能直接放std::packaged_tast,因此packaged_task放在堆上只创建一份用shared_ptr管理它shared_ptr是可以随便拷贝的成功获取返回值类型4#includelatch一直标红版本是C20也标红还不知道为什么5CPU利用率和运行效率对比两核不使用线程池一个CPU的利用率为100%使用线程池两个CPU的利用率为100%轻任务单线程 线程池 生成随机数、简单计算→ 线程池调度开销太大反而慢重任务线程池 单线程 排序、复杂计算、IO→ 线程池多核并行快几倍甚至十几倍16核核数越多效率越快6为什么使用多线程申请内存时比单线程申请内存的速度还要慢慢在什么地方为什么慢提前预留空间也并没有很大优化内存分配是天生的 “串行” 操作多线程争抢只会让它变慢。核心原因内存分配器malloc是 “带锁的”我们平时用的new/malloc底层都是调用操作系统的内存分配器最常见的是glibc的ptmalloc。为了保证线程安全它内部有一把全局锁。单线程场景线程直接调用malloc没有任何竞争也没有加锁、解锁的开销。分配器可以高效地找到合适的内存块直接返回。多线程场景多个线程同时调用malloc都要争抢这把锁。同一时间只有一个线程能拿到锁进行分配其他线程都必须阻塞等待。这种场景下多线程分配就变成了串行排队执行而且额外增加了大量的锁竞争、线程上下文切换开销。提前预留空间reserve为什么优化不大提前 reserve(m) 空间只是避免了内存拷贝但绕不开 malloc 本身的瓶颈。 reserve 只是一次性申请一块大内存避免了多次扩容的拷贝开销。 但在多线程下每个 vector 调用 reserve 时依然要去争抢全局锁所以锁竞争的问题还是存在。 当你一次性 reserve 大块内存时锁持有时间反而变长了其他线程等待的时间也更长了甚至可能比动态扩容更慢。7优化resize:创建并初始化把size设为 m创建 m 个元素[]访问100% 安全单线程和多线程同时用了resize预留空间但是使用的是自定义的随机生成函数且多线程用了门栓时间对比如下单线程和多线程同时用了resize预留空间以及#includerandom中自带的随机生成函数且多线程用了门栓时间对比如下reserve:看到的时间不准、是假的、是非法访问导致的开辟m 个空间size 依然是 0不创建任何元素正确编写后resize比reserve更快速单线程和多线程同时用了reserve预留空间但是使用的是自定义的随机生成函数且多线程用了门栓时间对比如下单线程和多线程同时用了reserve预留空间以及#includerandom中自带的随机生成函数且多线程用了门栓时间对比如下发现造成多线程生成数据耗时如此慢的主要原因是因为随机数生成,换成系统的随机函数更安全更高效8wait和wait_for9C 线程池的析构顺序与生命周期二、缓存线程池——CachedTreadPoolCPU占比不使用线程池时CPU占比CPU1处于100%使用缓存式线程池时,CPU占比最初提前用resize开辟了空间用了自定义的随机生成函数单线程和多线程同时提前用resize开辟了空间用了自定义的随机生成函数且多线程用了门栓时间对比如下第一次优化提前用rerserve开辟空间并且用了random库的随机生成单线程和多线程同时提前用reserve开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下第二次优化提前用resize开辟空间并且用了random库的随机生成单线程和多线程同时提前用resize开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下三、工作窃取线程池——WorkStealingPool1出现了死锁是因为在获取任务时使用了wait,导致了死锁2出现了无输出的情况Debugcmake -DCMAKE_BUILD_TYPEDebug ..make -j$(nproc)启动调试gdb ./threadpool3运行调试run发现卡住不动中断程序ctrlc查看所有线程info threads查看函数掉用栈thread 1bt#6 main()程序从main函数开始运行。#5 test2() at threadpoolTest3.cpp:97进入了test2()函数运行到第 97 行。#4 std::latch::wait()主线程在这里卡住了它在调用lat_steal.wait();#3 ~ #0 底层系统等待主线程已经被操作系统挂起不占 CPU不执行任何代码死等。主线程在 test2 函数的第 97 行也就是 lat_steal.wait() 处永远等不到任务完成。再查看一个非主线程的运行状态#0 futex_abstimed_wait_cancelable#1 pthread_cond_wait_common#2 pthread_cond_clockwait#3 std::condition_variable::wait_until#4 std::condition_variable::wait_for#5 SyncQueue::Task() at SyncQueue_4.hpp:98#6 WorkStealingPool::RunThread() at WorkStealingPool.hpp:50主线程卡死在latch.wait()→ 任务没执行完工作线程全部卡在SyncQueue::Task()→ 拿不到任务提交任务时用了默认队列容量 500而任务数是 10000 → 队列早就塞满了主线程在Put()时就已经阻塞后面的任务根本没提交进去队列里的任务数为 0 → 工作线程永远拿不到任务只能一直wait_for循环mypool() 队列太小装不下 10000 个任务 → 程序堵死卡死 → 无输出mypool(10000,16) 队列够大 → 能装下所有任务 → 正常跑 → 有输出单线程CPU利用率多线程CPU利用率最初提前用resize开辟了空间用了自定义的随机生成函数单线程和多线程同时提前用resize开辟了空间用了自定义的随机生成函数且多线程用了门栓时间对比如下第一次优化提前用rerserve开辟空间并且用了random库的随机生成单线程和多线程同时提前用reserve开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下第二次优化提前用resize开辟空间并且用了random库的随机生成单线程和多线程同时提前用resize开辟了空间并且多线程用了random库的随机生成数和门栓时间对比如下四、ScheduleThreadPoo——定时线程池1定时器出现了周期延迟TimerManage 的 loop 线程↓epoll_wait↓触发定时器 A → 执行回调↓触发定时器 B → 执行回调如果 func() 执行很慢后面的定时器必须等它执行完 周期就会越来越不准、越来越延迟定时器线程不阻塞Heavy task执行期间Fast task仍然每秒准时执行任务并发执行Fast task1和Fast task 2几乎同时执行时间差仅1ms周期性任务不受影响所有定时器都按预期间隔触发2使用map实现1个Timer管理N个Callback时间精度