Linux操作系统:线程池&踩坑修复

前言:本文是作者手搓的一个,中途有bug使用了opencode帮我修改并且加入了很多的注释,这个注释是长段的注释,不是小段的,小段的是我自己敲的,大段的注释是AI添加的,可以看一下,我的问题就可能是一个坑呢。

main.cc:

#include<iostream>

#include"ThreadPool.hpp"

#include<memory>

#include<task.hpp>

#include<unistd.h>

using namespace std;

int main()

{

int cnt=5;

unique_ptr<ThreadPool<task_t>> tpool=make_unique<ThreadPool<task_t>>(5);

tpool->Start();

while(cnt--)

{

sleep(1);

tpool->Enqueue(task1);

sleep(1);

tpool->Enqueue(task2);

}

tpool->Stop();

tpool->Wait();

return 0;

}

cond.hpp:

#include"Mutex.hpp"

#include<pthread.h>

class Cond

{

public:

Cond()

{

pthread_cond_init(&_cond,nullptr);

}

void Wait(Mutex& _mutex)

{

pthread_cond_wait(&_cond,_mutex.GetAddr());

}

void Notifytone()

{

pthread_cond_signal(&_cond);

}

void Notifyall()

{

pthread_cond_broadcast(&_cond);

}

~Cond()

{

pthread_cond_destroy(&_cond);

}

private:

pthread_cond_t _cond;

};

Mutex.hpp:

#ifndef MUTEX_HPP

#define MUTEX_HPP

#include<iostream>

#include<pthread.h>

using namespace std;

class Mutex

{

public:

Mutex()

{

pthread_mutex_init(&_lock,nullptr);

}

void Lock()

{

pthread_mutex_lock(&_lock);

}

void Unlock()

{

pthread_mutex_unlock(&_lock);

}

pthread_mutex_t* GetAddr()

{

return &_lock;

}

~Mutex()

{

pthread_mutex_destroy(&_lock);

}

private:

pthread_mutex_t _lock;

};

class Lockguard

{

public:

Lockguard(Mutex& mutex)

:_mutex(mutex)

{

_mutex.Lock();

}

~Lockguard()

{

_mutex.Unlock();

}

private:

Mutex& _mutex;

};

#endif

task.hpp:

#ifndef TASK_HPP

#define TASK_HPP

#include<iostream>

#include<functional>

using task_t=function<void()>;

void task1()

{

char name64;

pthread_getname_np(pthread_self(), name, sizeof(name));

cout<< "执行任务1: 打印消息 |" << name << "|"<<endl;

}

void task2()

{

char name64;

pthread_getname_np(pthread_self(), name, sizeof(name));

cout<< "执行任务2: 计算 1+1 = " << 1 + 1 << " |" << name << "|"<<endl;

}

#endif

Thread.hpp:

#include<iostream>

#include<functional>

#include<thread>

#include<string>

#include<unistd.h>

using namespace std;

using func_t=function<void()>;

int gnum=5;

class Thread

{

private:

static void* run(void *argc)

{

Thread *ts=(Thread *)argc;

ts->_func();

// 【进程被信号杀掉的根因】这里原来没有 return 语句。

// run() 返回类型是 void*,执行到函数末尾却不返回值属于未定义行为,

// g++(即使 -O0)会在函数末尾生成一条 ud2(非法指令)。

// 线程池运行期间 ThreadRun() 是死循环,run() 永远不返回,所以一直不暴露;

// 调用 Stop() 后工作线程 break 出循环、run() 返回,CPU 执行到 ud2,

// 内核便向进程发送 SIGILL(信号 4,Illegal instruction),

// 其默认动作是终止整个进程,因此看起来像"关闭线程池后莫名退出"。

// 修复:补上 return 语句(返回值会被 pthread 忽略)。

return nullptr;

}

public:

Thread(func_t t)

:_joinable(false),

_func(t)

{

_name="Thread-"+to_string(gnum--);

}

void start()

{

pthread_create(&_tid,nullptr,&run,this);

pthread_setname_np(_tid,_name.c_str());

_joinable=true;

}

void stop()

{

pthread_cancel(_tid);

}

void join() //阻塞,当线程刚刚创建的时候是可以阻塞的。

{

if(_joinable)

{

pthread_join(_tid,NULL);

// join 之后线程资源已回收,必须清掉标记;

// 否则析构函数 detach() 会对已 join 的线程再次 pthread_detach,

// 这属于未定义行为(可能返回 ESRCH,也可能崩溃)。

_joinable=false;

}

}

void detach() //分离,当线程可以阻塞就可以分离,但是分离了不能再次阻塞了

{

if (_joinable)

{

pthread_detach(_tid);

_joinable = false;

}

}

~Thread()

{

detach();

}

private:

pthread_t _tid;

string _name;

bool _joinable;

func_t _func;

};

ThreadPool.hpp:

#include "Mutex.hpp"

#include "cond.hpp"

#include <iostream>

#include <vector>

#include <queue>

#include <functional>

#include "Thread.hpp"

using namespace std;

using func_t = function<void()>;

template <class T>

class ThreadPool

{

private:

bool _queueempty()

{

if (_queue.size() == 0)

{

return true;

}

return false;

}

T pophelp()

{

T _task = _queue.front();

_queue.pop();

return _task;

}

void ThreadRun()

{

while (true)

{

T task;

// 任务队列没有任务,线程池不退出

{

Lockguard lockguard(_mutex);

while (_queueempty() && _isrunning)

{

_sleeping++;

_cond.Wait(_mutex);

_sleeping--;

}

// 任务队列没有任务,线程池退出-->此时真正退出

if (_queueempty() && !_isrunning)

break;

// 任务队列里有任务,执行任务

task = pophelp();

}

sleep(1);

task();

}

}

public:

ThreadPool(int cap) // 线程池创建几个线程

: _cap(cap), _isrunning(false), _sleeping(0)

{

for (int i = 0; i < _cap; i++)

{

threads.emplace_back(this()

{ this->ThreadRun(); });

}

}

void Start()

{

Lockguard _lockguard(_mutex);

_isrunning = true;

for (auto &thread : threads)

{

thread.start();

}

}

void Enqueue(T task)

{

Lockguard _lockguard(_mutex);

if(_isrunning==false) //当线程池不运行了就会直接退出, 完成存量任务后再退出

return ;

_queue.push(task);

_cond.Notifytone();

}

void Stop()

{

Lockguard _lockguard(_mutex);

if (_isrunning)

{

_isrunning = false;

cout << "关闭线程池" << endl;

if (_sleeping > 0)

{

_cond.Notifyall();

}

}

}

void Wait()

{

// 【这里有隐藏 bug】原来写的是 auto(按值拷贝):

// 每轮先拷贝一个 Thread 对象,对拷贝体 join 真实线程没问题,

// 但拷贝体析构时会对同一个已 join 的线程 detach;

// 而 vector 里原对象的 _joinable 仍为 true,析构时还会再 detach 一次,

// 属于未定义行为。改成引用即可,不要拷贝线程对象。

for (auto &Thread : threads)

{

Thread.join();

}

}

// 注意:析构函数为空,不会主动停线程/回收线程。

// 成员析构顺序与声明顺序相反,_cond、_mutex 会先于 threads 被销毁,

// 所以必须先 Stop() 再 Wait(),最后才允许对象析构(main 里正是这个顺序)。

// 若省略 Wait() 直接析构,工作线程可能访问已销毁的锁和条件变量。

~ThreadPool() {}

private:

int _cap;

vector<Thread> threads;

queue<T> _queue;

Mutex _mutex;

Cond _cond;

bool _isrunning;

int _sleeping;

};

运行效果:

结论:

本文实现了用线程池提前创建一堆线程然后去解决问题,需要注意的是一点就是线程池退出的逻辑,就是只有当线程池运行状态为退出并且队列为空时此时才会退出,当队列为空但是线程池不退出或者当队列不为空线程池退出这些都是不会退出的。

相关推荐
AI你一生一世2 小时前
当“版权主张”不再需要人类:从 Luanti 被下架看自动化维权的工程化风险
运维·人工智能·自动化·开源软件·内容分发·自动化维权·版权主张
云计算练习生2 小时前
什么是 Bash?Linux 默认 Shell 的核心功能
linux·操作系统·bash·shell·命令解释器
alphaTao2 小时前
LeetCode 每日一题 2026/10/19-2026/10/25
算法·leetcode
FlightYe3 小时前
音视频修炼之容器与协议(二):HLS和DASH流媒体
android·linux·ffmpeg·音视频·android runtime·dash
承渊政道3 小时前
Linux网络学习【从深入理解网络层原理到IP分片与重组全过程剖析】
linux·计算机网络·ubuntu·ip分片与重组
橘色的喵3 小时前
嵌入式Linux编程,如何把硬件中断交给用户态--UIO 的机制
linux·mcu·isr·中断·uio
强哥聊算力3 小时前
《算力江湖·DeepSeek》第四集:DeepSeek如何改变中国算力的采购逻辑?
大数据·运维·人工智能·ai·gpu算力
~城南花已开~3 小时前
Redfish 事件接收服务搭建指南(Ubuntu Server 实测)
linux·服务器·python·ubuntu
承渊政道3 小时前
新增一台服务器时还要重新装Nginx?Ansible只改hosts目标列表,同一份Playbook继续执行
运维·nginx·ansible·内网穿透·cpolar