当前位置: 首页 > news >正文

线程3.1

线程3.1

本节主要内容:

  1. 我们比较了c++11出现的线程和Linux的原生线程库,分析了c++11出现线程概念的必要性,以及和原生线程库在使用上的区别。我们自己也尝试做了一个简单封装,了解了大概是一个怎么样的思路。
  2. 我们比较重点的讲解了线程分离这个概念。我们分别讲了使用c++11的线程分离和使用原生线程库的线程分离。了解了线程分离的本质是:改变线程结束时的资源回收策略,而不是改变线程与进程的隶属关系。
    • 对比了两种线程分离的资源回收策略的不同。毕竟C++11是原生线程库的封装,除了原生线程库本应该有的作用和特点,肯定是会带有一些C++11的特性在的。比如RAII什么的。

番外链接:

有关于c++中线程类的介绍都在这:

C++ thread类-CSDN博客

本文涉及到的线程库函数的详细说明在:

  1. POSIX线程库–罗列核心函数-CSDN博客
  2. POSIX线程库–详细函数说明2.1-CSDN博客

完整系列:

线程1.1-CSDN博客

线程1.2-CSDN博客

线程2.0-CSDN博客



C++11线程与Linux原生线程库的比较

1. 核心区别分析

1.1 抽象层次差异

  • C++11线程:语言级别的线程抽象,提供跨平台的一致性接口
  • Linux原生线程(pthread):操作系统级别的线程实现,直接调用系统调用

1.2 可移植性对比

// C++11线程 - 跨平台
#include <thread>
std::thread t(func);// Linux pthread - 平台相关
#include <pthread.h>
pthread_t thread;
pthread_create(&thread, NULL, func, NULL);

1.3 资源管理方式

  • C++11:基于RAII原则,自动管理线程生命周期
  • pthread:需要手动管理线程的创建、终止和资源释放

2. C++11多线程编程示例

2.1 基本线程创建与管理

#include <iostream>
#include <thread>
#include <vector>void simple_worker(int id) {std::cout << "线程 " << id << " 开始执行" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "线程 " << id << " 执行完成" << std::endl;
}int main() {std::vector<std::thread> threads;// 创建多个线程for (int i = 0; i < 5; ++i) {threads.emplace_back(simple_worker, i);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "所有线程执行完毕" << std::endl;return 0;
}

2.2 互斥锁保护共享数据

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>std::mutex g_mutex;
int shared_counter = 0;void counter_worker(int increments) {for (int i = 0; i < increments; ++i) {std::lock_guard<std::mutex> lock(g_mutex);++shared_counter;}
}int main() {std::vector<std::thread> threads;// 创建4个线程,每个增加250次for (int i = 0; i < 4; ++i) {threads.emplace_back(counter_worker, 250);}for (auto& t : threads) {t.join();}std::cout << "最终计数器值: " << shared_counter << std::endl;return 0;
}

2.3 使用条件变量进行线程同步

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;void producer() {for (int i = 0; i < 10; ++i) {{std::lock_guard<std::mutex> lock(mtx);data_queue.push(i);std::cout << "生产数据: " << i << std::endl;}cv.notify_one();std::this_thread::sleep_for(std::chrono::milliseconds(100));}{std::lock_guard<std::mutex> lock(mtx);finished = true;}cv.notify_all();
}void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return !data_queue.empty() || finished; });while (!data_queue.empty()) {int data = data_queue.front();data_queue.pop();std::cout << "消费数据: " << data << std::endl;}if (finished && data_queue.empty()) {break;}}
}int main() {std::thread producer_thread(producer);std::thread consumer_thread(consumer);producer_thread.join();consumer_thread.join();std::cout << "生产消费完成" << std::endl;return 0;
}

2.4 异步任务处理

#include <iostream>
#include <future>
#include <vector>
#include <numeric>int compute_sum(const std::vector<int>& data) {std::cout << "异步计算开始..." << std::endl;int sum = std::accumulate(data.begin(), data.end(), 0);std::this_thread::sleep_for(std::chrono::seconds(2));return sum;
}int main() {std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 异步执行计算任务std::future<int> result = std::async(std::launch::async, compute_sum, data);std::cout << "主线程继续执行其他工作..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "主线程工作完成,等待异步结果..." << std::endl;// 获取异步结果(会阻塞直到计算完成)int sum = result.get();std::cout << "计算结果: " << sum << std::endl;return 0;
}

3. 深入对比分析

3.1 性能特性

  • C++11线程:在Linux上通常是对pthread的封装,有轻微性能开销
  • pthread:直接系统调用,性能最优,但编码复杂度高

3.2 错误处理机制

  • C++11:使用异常机制,更符合C++编程风格
  • pthread:返回错误码,需要手动检查

3.3 特性支持

  • C++11:提供更高级的抽象(future/promise、async等)
  • pthread:提供更底层的控制(线程属性、取消等)

4. 选择建议

  • 新项目:优先使用C++11线程,保证可移植性和现代C++特性
  • 性能关键:在需要极致性能时考虑pthread
  • 跨平台需求:必须使用C++11线程
  • Linux专有特性:如需要特定Linux线程特性,使用pthread

5. 简单总结

简单来说,c++中的线程其实就是对POSIX线程库中的函数的一个封装。我们可以使用下面这个例子来简单证明一下:

# 大家注意了,我们编译链接的指令中,没有使用-lpthread
test_thread:testThread.ccg++ -o $@ $^ .PHONY:clean
clean:rm -f test_thread

代码依旧使用:

#include <iostream>
#include <thread>
#include <vector>void simple_worker(int id) {std::cout << "线程 " << id << " 开始执行" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "线程 " << id << " 执行完成" << std::endl;
}int main() {std::vector<std::thread> threads;// 创建多个线程for (int i = 0; i < 5; ++i) {threads.emplace_back(simple_worker, i);}// 等待所有线程完成for (auto& t : threads) {t.join();}std::cout << "所有线程执行完毕" << std::endl;return 0;
}

当我们使用编译代码的时候就会发现:

lx@emperor:~/working/d47/threadtest$ make
g++ -o test_thread testThread.cc 
/usr/bin/ld: /tmp/ccCkZ7ws.o: in function `std::thread::thread<void (&)(int), int&, void>(void (&)(int), int&)':
testThread.cc:(.text._ZNSt6threadC2IRFviEJRiEvEEOT_DpOT0_[_ZNSt6threadC5IRFviEJRiEvEEOT_DpOT0_]+0x37): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
make: *** [Makefile:2: test_thread] Error 1

这其中呢最明显的,大家都能看明白的一个东西就是:

undefined reference to `pthread_create'

pthread_create是POSIX线程库中的函数,为什么使用c++的thread类的时候会出现这样一个报错的?

答:很明显就是因为c++是对POSIX线程库的一个封装嘛,所以c++中的thread类的实现的底层就是POSIX线程库中的函数接口。

所以当我们编译链接的时候,没有声明链接pthread线程库,那么C++中的thread类就没用了。所以我们在使用c++中的thread类的时候,编译链接中的选项依旧要带有-lpthread


其次就是c++为什么要对pthread库封装呢?这个问题的答案主要就是:实现语言的跨平台性。防止在Linux中写的代码在Windows中用不了。这就是c++封装pthread库的一个主要作用。

同理,其他语言如果也携带有线程库,那么大概率也都是封装的各个平台提供的线程库。

6. 简单复刻C++对pthread库的封装

Makefile

test_thread:testThread.ccg++ -o $@ $^ -lpthread -std=c++11.PHONY:clean
clean:rm -f test_thread

第一版:使用函数对象

Thread.hpp
#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{template<typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template<typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func, T data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){}// 类成员函数,形参是有this指针的!!// 所以我们写成静态成员函数static void *threadroutine(void *args) {Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n){_stop = false;return true;}else{return false;}}void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T _data;  func_t<T> _func;bool _stop;};
} // namespace ThreadModule#endif

template<typename T>
using func_t = std::function<void(T&)>;

这行代码的意思是:

  • 定义一个模板别名 func_t
  • 对于任何类型 Tfunc_t<T>等价于 std::function<void(T&)>

我们用一个类将线程封装起来,其中私有成员变量有用户态的线程TID,可以有线程名称,必须得有函数对象func_t<T> _func;。此外,我们还可以设置一个用来标记线程运行状态的标识符bool _stop;

Thread(func_t<T> func, T data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){}

使用构造函数来初始化线程。

这里的重点内容是:

public:
void Excute()
{_func(_data);
}
// 类成员函数,形参是有this指针的!!
// 所以我们写成静态成员函数
static void *threadroutine(void *args) 
{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;
}
bool Start()
{int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n){_stop = false;return true;}else{return false;}
}

为什么我们不直接封装pthread_create,而是要用一个静态成员函数来绕一下路?

理由:

第一点:我们封装线程类的时候,使用的是函数对象func_t<T> _func;,而不是函数指针,所以我们不能直接将函数对象作为pthread_create的参数传过去。所以我们必须得写一个中间函数,在代码中,这个中间函数就是threadroutine。我们用这个中间函数来使用函数对象。然后将threadroutine作为pthread_create函数的参数传过去。

第二点:由于pthread_create函数的参数要求:线程函数必须符合此签名:void* function_name(void* arg)。参数和返回值都是void*的线程函数才能作为参数。

但是类成员函数中都会有一个默认参数this指针。这就会导致类成员函数的签名不符合要求(函数参数不是void*,或者说,多了一个函数参数)。

所以这个中间函数threadroutine,就不能是类成员函数。为了解决这个问题,我们给它设置为了静态成员函数,这样它的函数参数中就不会有this指针了。从而符合pthread_create函数对参数的要求。

但是

因为threadroutine没有this指针,所以这个中间函数就无法直接访问类内私有成员(函数对象)。

所以

为了解决这个问题,我们使用pthread_create函数的时候,要把this指针传过去,这样就能访问类内的成员了。


上面的代码,我们不是将创建一个线程对象和启动线程分开写的嘛,我们也可以将这个过程合并在一起,我们可以这样写:

#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{template<typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template<typename T>class Thread{public:// void Excute()// {//     _func(_data);// }public:Thread(func_t<T> func, T data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n)_stop = false;}static void *threadroutine(void *args) {Thread<T> *self = static_cast<Thread<T> *>(args);//self->Excute(); ↓self->_func(self->_data);return nullptr;}// bool Start()// {//     int n = pthread_create(&_tid, nullptr, threadroutine, this);//     if(!n)//     {//         _stop = false;//         return true;//     }//     else//     {//         return false;//     }// }void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T _data;func_t<T> _func;bool _stop;};
} // namespace ThreadModule#endif

相应的:testThread.cc

#include <iostream>
#include <vector>
#include "Thread.hpp"using namespace ThreadModule;void print(int& cnt)
{while (cnt){std::cout << "hello I am myself thread, cnt: " << cnt-- << std::endl;sleep(1);}
}const int num = 10;int main()
{std::vector<Thread<int> > threads;// 1. 创建一批线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(print, 10, name);}// // 2. 启动 一批线程// for (auto &thread : threads)// {//     thread.Start();// }// 3. 等待一批线程for (auto &thread : threads){thread.Join();std::cout << "wait thread done, thread is: " << thread.name() << std::endl;}return 0;
}

我们来看看都变了什么:

public:// void Excute()// {//     _func(_data);// }public:Thread(func_t<T> func, T data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n)_stop = false;}static void *threadroutine(void *args) {Thread<T> *self = static_cast<Thread<T> *>(args);//self->Excute(); ↓self->_func(self->_data);return nullptr;}// bool Start()// {//     int n = pthread_create(&_tid, nullptr, threadroutine, this);//     if(!n)//     {//         _stop = false;//         return true;//     }//     else//     {//         return false;//     }// }

我们在构造函数初始化完类内私有成员变量后,我们直接调用pthread_create,相当于把Start省略掉了。然后threadroutine也有点变化,我们其实可以在这个函数中直接使用函数对象。

不过有一点我们需要注意一下:

第一:静态成员函数不能直接访问类内成员,因为它没有this指针。

但是:我们传过去了一个this指针,所以静态成员函数就可以通过这个传过来的this指针(在函数中其实也就是self指针嘛),访问类内的成员(包括私有成员)。

那么我们到底需要注意什么呢?

一开始在改代码的时候,我写成了:self->_func(_data);。看明白没?

在使用函数对象的时候,我使用self指针,但是,我使用类内私有成员变量_data的时候,忘记了。所以编译就报错了。

所以大家需要注意一下,有时候就是这些不起眼的地方,容易出错。

应该写成这样:self->_func(self->_data);就对了。


testThread.cc
#include <iostream>
#include <vector>
#include "Thread.hpp"using namespace ThreadModule;void print(int& cnt)
{while (cnt){std::cout << "hello I am myself thread, cnt: " << cnt-- << std::endl;sleep(1);}
}const int num = 10;int main()
{std::vector<Thread<int> > threads;// 1. 创建一批线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(print, 10, name);}// 2. 启动 一批线程for (auto &thread : threads){thread.Start();}// 3. 等待一批线程for (auto &thread : threads){thread.Join();std::cout << "wait thread done, thread is: " << thread.name() << std::endl;}return 0;
}

第二版:使用函数指针

Thread.hpp
//#ifndef __THREAD_HPP__
//#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{// 定义函数指针类型// 函数指针类型名是:worktypedef void*(* work_t)(void*);template<typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template<typename T>class Thread{public:// void Excute()// {//     _func(_data);// }public:Thread(work_t work, T data, const std::string &name="none-name"): _work(work), _data(data), _threadname(name), _stop(true){int n = pthread_create(&_tid, nullptr, _work, &_data);if(!n)_stop = false;}// static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!// {//     Thread<T> *self = static_cast<Thread<T> *>(args);//     //self->Excute(); ↓//     self->_func(self->_data);//     return nullptr;// }// bool Start()// {//     int n = pthread_create(&_tid, nullptr, threadroutine, this);//     if(!n)//     {//         _stop = false;//         return true;//     }//     else//     {//         return false;//     }// }void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T _data;//func_t<T> _func;work_t _work;bool _stop;};
} // namespace ThreadModule//#endif
testThread.cc
#include <iostream>
#include <vector>
#include "Thread.hpp"using namespace ThreadModule;void* print(void* arg)
{int cnt = *(int*)arg;while (cnt){std::cout << "hello I am myself thread, cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}const int num = 10;int main()
{std::vector<Thread<int> > threads;// 1. 创建一批线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(print, 10, name);}// // 2. 启动 一批线程// for (auto &thread : threads)// {//     thread.Start();// }// 3. 等待一批线程for (auto &thread : threads){thread.Join();std::cout << "wait thread done, thread is: " << thread.name() << std::endl;}return 0;
}
主要变化:

这一次我们封装线程类,类内的私有成员从函数对象func_t<T> _func;变成了函数指针work_t _work;

但是吧,我感觉使用函数指针来封装,方便的是封装的人,而不是用户。感觉就是封装了和没封装没啥区别。

封装后该怎么写还是怎么写,还是和封装之前一样,没有让使用变得更简洁简单。

就那那个print函数来说吧。

第一版封装我可以按照正常函数,按照我既定的逻辑去写:

void print(int& cnt)
{while (cnt){std::cout << "hello I am myself thread, cnt: " << cnt-- << std::endl;sleep(1);}
}

这个函数的参数和返回值,我想它是怎么样的就是怎么样的。

但是当我们使用函数指针来封装的时候:

void* print(void* arg)
{int cnt = *(int*)arg;while (cnt){std::cout << "hello I am myself thread, cnt: " << cnt-- << std::endl;sleep(1);}return nullptr;
}

就必须按照要求写了,返回值和参数必须是void*,所以这种封装对于用户来说其实是挺多余的。没啥用。

当然了,我们这里只是做学习用途,学学看,了解了解,总没坏处。



C++11线程分离

1. 线程分离的基本概念

1.1 什么是线程分离

线程分离是一种线程管理机制,它改变了线程结束时的资源回收方式。当一个线程被设置为分离状态后,该线程结束时系统会自动回收其资源,而不需要其他线程调用join来等待其结束。

1.2 分离线程的核心特性

#include <iostream>
#include <thread>
#include <chrono>void detached_worker() {std::cout << "分离线程开始工作..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << "分离线程工作完成" << std::endl;
}int main() {std::thread t(detached_worker);// 将线程设置为分离状态t.detach();std::cout << "主线程继续执行,不等待分离线程" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));// 注意:分离后不能再调用join()// t.join(); // 错误!会导致程序崩溃return 0;
}

2. 关键问题深度分析

2.1 分离线程后的现象观察

实验代码:观察分离线程的行为

#include <iostream>
#include <thread>
#include <chrono>void long_running_task(int id) {for (int i = 0; i < 5; ++i) {std::cout << "线程" << id << "执行第" << i << "步" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout << "线程" << id << "正常结束" << std::endl;
}int main() {std::thread t1(long_running_task, 1);std::thread t2(long_running_task, 2);// 分离第一个线程,等待第二个线程t1.detach();t2.join();  // 主线程等待t2结束std::cout << "主线程退出" << std::endl;return 0;
}

观察结果分析:

  • 线程t1被分离后,主线程不会等待它完成
  • 线程t2需要join,主线程会等待t2完成
  • 主线程退出时,如果分离线程还在运行,会被强制终止

2.2 进程退出对分离线程的影响

验证代码:进程退出时分离线程的命运

#include <iostream>
#include <thread>
#include <chrono>void infinite_worker() {int count = 0;while (true) {std::cout << "分离线程运行中...计数: " << ++count << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));if (count >= 5) {std::cout << "分离线程完成5次循环" << std::endl;break;}}
}int main() {std::thread t(infinite_worker);t.detach();std::cout << "主线程快速退出,观察分离线程是否继续" << std::endl;// 主线程立即退出,不等待分离线程return 0;
}

运行结果:

lx@emperor:~/working/d47/threadtest$ ./test_thread 
主线程快速退出,观察分离线程是否继续分离线程运行中...计数: lx@emperor:~/working/d47/threadtest$ ./test_thread 
主线程快速退出,观察分离线程是否继续
分离线程运行中...计数: 1lx@emperor:~/working/d47/threadtest$ ./test_thread 
主线程快速退出,观察分离线程是否继续分离线程运行中...计数: 

关键发现:

  • 进程是所有线程的容器,进程退出意味着所有线程终止
  • 分离线程并不会获得"独立性",仍然受进程生命周期约束
  • 这是操作系统的进程管理机制决定的,不是线程库的特性

3. 线程分离的正确理解

3.1 分离的本质是什么

线程分离的本质是改变线程结束时的资源回收策略,而不是改变线程与进程的隶属关系。

资源管理对比:

// 非分离线程(需要手动join)
std::thread t(worker);
// ... 其他操作
t.join();  // 必须调用,否则资源泄漏// 分离线程(自动回收)
std::thread t(worker);
t.detach();  // 分离后,线程结束自动回收资源
// 不需要也不能调用join

3.2 共享资源的所有权关系

即使线程被分离,它仍然:

  • 共享进程的地址空间
  • 访问相同的内存区域
  • 使用相同的文件描述符
  • 受相同的进程权限限制

4. 线程分离的实际应用场景

4.1 服务器编程中的典型应用

后台任务处理示例:

#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>std::mutex log_mutex;void background_logger(const std::string& message) {// 模拟耗时的日志写入操作std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lock(log_mutex);std::cout << "[后台日志] " << message << std::endl;
}void handle_client_request(int client_id) {// 创建分离线程处理日志记录std::thread log_thread(background_logger, "客户端" + std::to_string(client_id) + "的请求已处理");log_thread.detach();  // 不关心日志线程何时完成// 主业务逻辑继续执行std::cout << "处理客户端" << client_id << "的请求..." << std::endl;
}int main() {// 模拟处理多个客户端请求for (int i = 1; i <= 3; ++i) {handle_client_request(i);std::this_thread::sleep_for(std::chrono::milliseconds(500));}// 给后台线程一些时间完成std::this_thread::sleep_for(std::chrono::seconds(2));return 0;
}

4.2 常驻进程中的线程使用模式

服务端守护线程示例:

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>std::atomic<bool> should_exit{false};void signal_handler() {// 等待退出信号std::cout << "信号处理线程启动" << std::endl;while (!should_exit) {std::this_thread::sleep_for(std::chrono::seconds(1));// 模拟检查退出条件static int count = 0;if (++count > 5) {should_exit = true;std::cout << "收到退出信号" << std::endl;}}
}void worker_thread(int id) {std::cout << "工作线程" << id << "启动" << std::endl;while (!should_exit) {// 模拟工作std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "工作线程" << id << "执行任务..." << std::endl;}std::cout << "工作线程" << id << "退出" << std::endl;
}int main() {// 创建信号处理线程(分离)std::thread signal_thread(signal_handler);signal_thread.detach();// 创建工作线程(不分离,主线程需要管理)std::thread worker1(worker_thread, 1);std::thread worker2(worker_thread, 2);// 主线程等待工作线程结束worker1.join();worker2.join();std::cout << "所有线程正常退出" << std::endl;return 0;
}

5. 现实世界类比理解

餐厅服务类比

  • 进程 = 整个餐厅
  • 主线程 = 餐厅老板(常驻,管理全局)
  • 工作线程 = 服务员(处理具体任务)
  • 线程分离 = 经理不关心某个服务员何时下班(系统自动处理)

这个分离的服务员依旧是属于餐厅中的一个员工。只不过老板不关心这个员工的工作情况和结果。如果餐厅倒闭了,这个员工也就没有工作了。

6. 使用注意事项和最佳实践

6.1 分离线程的陷阱

// 错误示例:访问已销毁的局部变量
void problematic_detach() {int local_data = 42;std::thread t([&local_data]() {std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << local_data << std::endl;  // 危险!local_data可能已销毁});t.detach();
}  // 函数返回,local_data被销毁,但分离线程可能还在运行// 正确做法:值捕获或确保生命周期
void safe_detach() {auto data = std::make_shared<int>(42);std::thread t([data]() {  // 值捕获,共享所有权std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << *data << std::endl;  // 安全});t.detach();
}

6.2 分离线程的适用场景总结

  1. 后台任务:日志记录、数据备份等
  2. 事件监听:网络心跳包、信号处理等
  3. 一次性任务:不需要结果的计算任务
  4. 资源清理:定时清理缓存、连接池维护等


POSIX线程分离

POSIX线程库中的线程分离函数的详细说明在:POSIX线程库–详细函数说明2.1-CSDN博客

在线程2中我们讲了线程的退出和线程的等待,但是还没有讲线程的分离,所以这一节我们来看看线程分离的内容。

1. 线程分离的基本概念与POSIX接口

1.1 什么是线程分离

线程分离是POSIX线程库提供的一种线程管理机制,通过pthread_detach()函数实现。分离后的线程在结束时系统会自动回收其资源,无需其他线程调用pthread_join()进行等待。

1.2 POSIX线程分离接口

#include <pthread.h>int pthread_detach(pthread_t thread);
  • 返回值:成功返回0,失败返回错误码
  • 参数:要分离的线程ID

2. 关键问题深度实验分析

2.1 分离线程后的现象观察实验

实验代码:观察分离线程的行为

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>void* long_running_task(void* arg) {int id = *(int*)arg;for (int i = 0; i < 5; ++i) {printf("线程%d执行第%d步\n", id, i);sleep(1);}printf("线程%d正常结束\n", id);return NULL;
}int main() {pthread_t t1, t2;int id1 = 1, id2 = 2;// 创建两个线程pthread_create(&t1, NULL, long_running_task, &id1);pthread_create(&t2, NULL, long_running_task, &id2);// 分离第一个线程,等待第二个线程if (pthread_detach(t1) != 0) {perror("pthread_detach失败");return -1;}// 等待第二个线程结束if (pthread_join(t2, NULL) != 0) {perror("pthread_join失败");return -1;}printf("主线程退出\n");return 0;
}

编译命令:

gcc -o thread_detach thread_detach.c -lpthread

观察结果分析:

  • 线程t1被分离后,主线程不会等待它完成
  • 线程t2需要pthread_join,主线程会等待t2完成
  • 分离线程t1的输出可能不完整,因为主线程退出时进程终止

2.2 进程退出对分离线程的影响验证

验证代码:进程退出时分离线程的命运

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>void* infinite_worker(void* arg) {int count = 0;while (1) {printf("分离线程运行中...计数: %d\n", ++count);sleep(1);if (count >= 3) {printf("分离线程完成3次循环\n");break;}}printf("分离线程正常退出\n");return NULL;
}int main() {pthread_t t;if (pthread_create(&t, NULL, infinite_worker, NULL) != 0) {perror("线程创建失败");return -1;}// 立即分离线程if (pthread_detach(t) != 0) {perror("线程分离失败");return -1;}printf("主线程快速退出,观察分离线程是否继续\n");// 主线程立即退出,不等待分离线程// 这里给一点时间观察现象sleep(1);return 0;
}

关键发现验证:

  • 进程退出时,所有线程(包括分离线程)都会被强制终止
  • 分离线程的"分离"只影响资源回收,不影响生命周期依赖

3. 线程分离的底层机制深度解析

3.1 线程状态管理机制

线程状态转换示意图:

创建线程(pthread_create)↓
可结合状态(joinable) ←→ 运行中↓ (pthread_detach)
分离状态(detached) ←→ 运行中↓ (线程结束)
自动资源回收

3.2 资源回收机制对比

非分离线程的资源管理:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void* worker(void* arg) {printf("工作线程开始\n");sleep(2);printf("工作线程结束\n");return NULL;
}int main() {pthread_t t;// 创建线程pthread_create(&t, NULL, worker, NULL);// 必须调用join,否则资源泄漏pthread_join(t, NULL);printf("主线程结束\n");return 0;
}

分离线程的自动资源回收:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void* worker(void* arg) {printf("分离工作线程开始\n");sleep(2);printf("分离工作线程结束\n");return NULL;
}int main() {pthread_t t;pthread_create(&t, NULL, worker, NULL);// 分离线程,自动回收资源pthread_detach(t);// 不需要也不能调用pthread_join// pthread_join(t, NULL); // 错误!// 给分离线程时间完成sleep(3);printf("主线程结束\n");return 0;
}

3.3 资源回收策略注意事项

详细说明在POSIX线程库–详细函数说明2.1-CSDN博客中pthread_detach函数部分–>资源回收的详细说明

资源类型系统是否自动回收管理责任示例
线程栈空间✅ 是系统int local_var; char buffer[100];
线程描述符✅ 是系统线程ID、状态信息等
动态分配内存❌ 否程序员malloc(), calloc()分配的内存
文件描述符❌ 否程序员open(), fopen()返回的fd
互斥锁等同步对象❌ 否程序员pthread_mutex_t, 需要手动destroy

系统自动回收的资源:

  • 线程栈空间(局部变量)
  • 线程描述符和内核数据结构
  • 线程状态信息等系统资源

需要手动管理的资源:

  • malloc(), calloc()分配的堆内存
  • fopen()打开的文件描述符
  • pthread_mutex_init()创建的互斥锁等同步对象
  • 其他任何动态分配的资源

关键原则:

  1. 分离线程 ≠ 自动内存管理
  2. 动态分配的资源必须显式释放
  3. 文件描述符等系统资源必须正确关闭
  4. 在线程结束前确保所有资源都被妥善处理

4. 实际应用场景与POSIX实现

4.1 服务器后台任务处理

POSIX线程实现的日志服务示例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <time.h>pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;void* background_logger(void* arg) {char* message = (char*)arg;// 模拟耗时的日志写入操作sleep(1);pthread_mutex_lock(&log_mutex);time_t now = time(NULL);printf("[%ld] 后台日志: %s\n", now, message);pthread_mutex_unlock(&log_mutex);return NULL;
}void handle_client_request(int client_id) {char message[100];snprintf(message, sizeof(message), "客户端%d的请求已处理", client_id);pthread_t log_thread;// 创建日志线程if (pthread_create(&log_thread, NULL, background_logger, strdup(message)) != 0) {perror("日志线程创建失败");return;}// 分离日志线程if (pthread_detach(log_thread) != 0) {perror("日志线程分离失败");}// 主业务逻辑继续执行printf("处理客户端%d的请求...\n", client_id);
}int main() {// 模拟处理多个客户端请求for (int i = 1; i <= 3; ++i) {handle_client_request(i);sleep(1);}// 给后台线程一些时间完成sleep(3);pthread_mutex_destroy(&log_mutex);return 0;
}

4.2 常驻进程的线程管理模式

服务端守护进程的POSIX实现:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <stdatomic.h>atomic_int should_exit = 0;void* signal_handler(void* arg) {printf("信号处理线程启动\n");for (int i = 0; i < 5; ++i) {if (should_exit) break;sleep(1);printf("检查系统状态...%d/5\n", i + 1);// 模拟收到退出信号if (i == 4) {should_exit = 1;printf("收到退出信号\n");}}return NULL;
}void* worker_thread(void* arg) {int id = *(int*)arg;printf("工作线程%d启动\n", id);while (!should_exit) {sleep(2);printf("工作线程%d执行任务...\n", id);}printf("工作线程%d退出\n", id);return NULL;
}int main() {pthread_t signal_thread, worker1, worker2;int id1 = 1, id2 = 2;// 创建信号处理线程(分离)if (pthread_create(&signal_thread, NULL, signal_handler, NULL) != 0) {perror("信号线程创建失败");return -1;}pthread_detach(signal_thread);// 创建工作线程(不分离,需要join)pthread_create(&worker1, NULL, worker_thread, &id1);pthread_create(&worker2, NULL, worker_thread, &id2);// 主线程等待工作线程结束pthread_join(worker1, NULL);pthread_join(worker2, NULL);printf("所有线程正常退出\n");return 0;
}

5. 深入理解线程分离的本质

5.1 线程与进程的关系再认识

验证线程共享进程资源的实验:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>int global_data = 100;  // 全局变量,所有线程共享void* detached_worker(void* arg) {printf("分离线程PID: %d, 线程ID: %lu\n", getpid(), (unsigned long)pthread_self());// 修改全局数据global_data = 200;printf("分离线程修改global_data为: %d\n", global_data);sleep(2);printf("分离线程看到的global_data: %d\n", global_data);return NULL;
}int main() {printf("主线程PID: %d, 线程ID: %lu\n", getpid(), (unsigned long)pthread_self());pthread_t t;pthread_create(&t, NULL, detached_worker, NULL);pthread_detach(t);// 主线程也修改全局数据sleep(1);global_data = 300;printf("主线程修改global_data为: %d\n", global_data);// 等待分离线程结束sleep(2);printf("最终global_data: %d\n", global_data);return 0;
}

实验结果证明:

  • 分离线程与主线程有相同的PID(属于同一进程)
  • 分离线程仍然共享全局数据
  • 线程分离不改变资源共享的本质

5.2 分离线程的错误处理模式

健壮的线程分离代码示例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>void* worker_with_cleanup(void* arg) {// 线程清理处理程序pthread_cleanup_push([](void* arg) {printf("执行清理操作: %s\n", (char*)arg);}, "释放资源");printf("工作线程开始\n");for (int i = 0; i < 3; i++) {printf("工作进度: %d/3\n", i + 1);sleep(1);// 模拟可能出错的情况if (i == 1) {printf("发生错误,线程提前退出\n");pthread_exit(NULL);}}pthread_cleanup_pop(1);  // 执行清理函数printf("工作线程正常结束\n");return NULL;
}int main() {pthread_t t;int ret;// 创建线程ret = pthread_create(&t, NULL, worker_with_cleanup, NULL);if (ret != 0) {fprintf(stderr, "线程创建失败: %s\n", strerror(ret));return -1;}// 分离线程ret = pthread_detach(t);if (ret != 0) {fprintf(stderr, "线程分离失败: %s\n", strerror(ret));// 分离失败时需要joinpthread_join(t, NULL);return -1;}printf("主线程继续执行,不等待工作线程\n");sleep(4);  // 确保工作线程有足够时间完成return 0;
}

6. 最佳实践

POSIX线程分离的最佳实践

  1. 分离时机选择
// 创建后立即分离(推荐)
pthread_t t;
pthread_create(&t, NULL, worker, NULL);
pthread_detach(t);// 或者在线程内部自我分离
void* worker(void* arg) {pthread_detach(pthread_self());// ... 工作逻辑
}
  1. 错误处理模式
int result = pthread_detach(thread_id);
if (result != 0) {if (result == EINVAL) {// 线程不是可结合状态} else if (result == ESRCH) {// 线程ID不存在}// 相应的错误处理
}
  1. 资源管理注意事项
    • 分离线程中避免使用主线程栈上的变量
    • 确保分离线程访问的资源生命周期足够长
    • 使用适当的同步机制保护共享资源


提问:

使用c++中的线程类函数来分离线程和使用POSIX线程库中的函数来分离线程有什么不同嘛?尤其是资源回收方面。我比较好奇的就是:我知道使用POSIX线程库回收线程资源是只回收部分资源的(比如线程栈空间,线程描述符合内核数据结构等待),是不会回收线程中申请的堆内存等资源的。那么使用c++中的线程类函数,它的资源回收策略是什么样的呢?也是和POSIX线程库中的资源回收策略一样吗?

回答:

C++11线程与POSIX线程分离机制对比

1. 底层实现架构分析

1.1 C++11线程库的底层依赖

// C++11线程库在Linux上的典型实现架构
// std::thread 底层通常封装pthread
class thread {
private:pthread_t _M_id;  // 底层pthread句柄// ... 其他成员
public:// 构造函数、析构函数等
};

1.2 资源回收机制对比架构

C++11 std::thread↓ (封装)
POSIX pthread↓ (系统调用)
Linux内核线程

2. 资源回收策略深度解析

2.1 POSIX线程资源回收机制

POSIX线程资源分类:

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>void* pthread_worker(void* arg) {// 线程栈上分配的资源(自动回收)int stack_array[100];// 堆上分配的资源(需要手动管理)int* heap_data = malloc(100 * sizeof(int));// 文件资源(需要手动管理)FILE* file = fopen("temp.txt", "w");printf("线程运行中...\n");sleep(2);// 必须手动释放堆资源!free(heap_data);fclose(file);return NULL;
}int main() {pthread_t t;pthread_create(&t, NULL, pthread_worker, NULL);pthread_detach(t);  // 只回收线程系统资源// 注意:detach不回收heap_data和file资源!sleep(3);return 0;
}

POSIX线程分离回收的具体资源:

  • ✅ 线程内核数据结构
  • ✅ 线程栈空间
  • ✅ 线程描述符
  • ❌ 堆内存分配
  • ❌ 文件描述符
  • ❌ 动态库资源

2.2 C++11线程资源回收机制

C++11线程资源管理示例:

#include <iostream>
#include <thread>
#include <memory>
#include <fstream>void cpp11_worker() {// 栈上对象(自动析构)std::string stack_string = "栈上字符串";// 智能指针管理的堆资源(自动释放)auto heap_data = std::make_unique<int[]>(100);// RAII文件对象(自动关闭)std::ofstream file("temp.txt");std::cout << "C++11线程运行中..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));// 不需要手动释放!RAII自动处理
}int main() {std::thread t(cpp11_worker);t.detach();  // 分离线程std::this_thread::sleep_for(std::chrono::seconds(3));return 0;
}

3. 核心差异深度分析

3.1 资源管理哲学的根本不同

POSIX线程(C风格)资源管理:

// 手动资源管理模式
void* pthread_example(void* arg) {// 资源获取void* resource1 = acquire_resource_1();void* resource2 = acquire_resource_2();// 使用资源use_resources(resource1, resource2);// 必须手动释放!release_resource_2(resource2);release_resource_1(resource1);return NULL;
}

C++11线程(RAII模式)资源管理:

// RAII自动资源管理
void cpp11_example() {// 资源获取即初始化RAII_Resource resource1 = acquire_resource_1();RAII_Resource resource2 = acquire_resource_2();// 使用资源use_resources(resource1, resource2);// 自动释放!析构函数处理
}

3.2 内存回收策略对比实验

实验代码:验证资源回收差异

#include <iostream>
#include <thread>
#include <memory>
#include <cstdlib>// C++11线程函数
void cpp11_memory_test() {auto smart_ptr = std::make_unique<int>(42);std::cout << "C++11线程: 智能指针管理的内存" << std::endl;// 智能指针离开作用域自动释放
}// 用于pthread的函数
void* pthread_memory_test(void* arg) {int* raw_ptr = new int(42);  // 原始指针std::cout << "Pthread: 原始指针分配的内存" << std::endl;// 必须手动删除!delete raw_ptr;return NULL;
}int main() {std::cout << "=== C++11线程测试 ===" << std::endl;{std::thread t1(cpp11_memory_test);t1.detach();}std::cout << "=== Pthread测试 ===" << std::endl;{pthread_t t2;pthread_create(&t2, NULL, pthread_memory_test, NULL);pthread_detach(t2);}std::this_thread::sleep_for(std::chrono::seconds(1));// 使用valgrind等工具检测内存泄漏std::cout << "测试完成,检查内存泄漏..." << std::endl;return 0;
}

4. 异常安全性的关键差异

4.1 C++11线程的异常安全保证

异常安全示例:

#include <iostream>
#include <thread>
#include <memory>
#include <stdexcept>void exception_worker() {auto resource = std::make_unique<int[]>(100);try {// 可能抛出异常的操作if (rand() % 2) {throw std::runtime_error("随机错误!");}std::cout << "操作成功" << std::endl;}catch (const std::exception& e) {std::cout << "捕获异常: " << e.what() << std::endl;// resource仍然会被正确释放!}// 无论是否异常,resource都会被自动释放
}int main() {std::thread t(exception_worker);t.detach();std::this_thread::sleep_for(std::chrono::seconds(1));return 0;
}

4.2 POSIX线程的异常安全问题

POSIX线程异常处理挑战:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>// C语言没有异常机制,需要使用错误码
void* pthread_exception_unsafe(void* arg) {int* data = malloc(100 * sizeof(int));// 模拟错误情况if (rand() % 2) {// 在C++中抛出异常(危险!)// throw std::runtime_error("错误"); // 这会导致未定义行为// 必须使用错误码free(data);  // 手动释放return NULL;}// 正常处理free(data);  // 必须手动释放return NULL;
}

5. 实际应用中的资源管理策略

5.1 C++11最佳实践:RAII完全体

完整的RAII资源管理示例:

#include <iostream>
#include <thread>
#include <memory>
#include <vector>
#include <fstream>class ThreadSafeLogger {
private:std::ofstream file;std::mutex mutex;public:ThreadSafeLogger(const std::string& filename) : file(filename) {}void log(const std::string& message) {std::lock_guard<std::mutex> lock(mutex);if (file.is_open()) {file << message << std::endl;}}// 析构函数自动关闭文件~ThreadSafeLogger() {if (file.is_open()) {file.close();}}
};void raii_worker(std::shared_ptr<ThreadSafeLogger> logger) {// 所有资源都是RAII管理的auto data = std::make_shared<std::vector<int>>(100, 42);logger->log("线程开始工作");// 模拟工作std::this_thread::sleep_for(std::chrono::milliseconds(500));logger->log("线程工作完成");// 所有资源自动释放:// - data的智能指针// - logger的共享指针// - 函数栈上的所有对象
}int main() {auto logger = std::make_shared<ThreadSafeLogger>("log.txt");std::thread t(raii_worker, logger);t.detach();std::this_thread::sleep_for(std::chrono::seconds(1));// logger在main结束时自动释放return 0;
}

5.2 POSIX资源管理的最佳实践

POSIX线程的完整资源管理:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>typedef struct {int* data;size_t size;FILE* log_file;
} thread_resources_t;void cleanup_resources(void* arg) {thread_resources_t* res = (thread_resources_t*)arg;printf("清理线程资源...\n");if (res->data) {free(res->data);res->data = NULL;}if (res->log_file) {fclose(res->log_file);res->log_file = NULL;}free(res);
}void* pthread_worker(void* arg) {thread_resources_t* resources = malloc(sizeof(thread_resources_t));resources->data = NULL;resources->log_file = NULL;// 注册清理函数pthread_cleanup_push(cleanup_resources, resources);// 分配资源resources->data = malloc(100 * sizeof(int));resources->log_file = fopen("pthread_log.txt", "w");if (!resources->data || !resources->log_file) {pthread_exit(NULL);}// 使用资源fprintf(resources->log_file, "Pthread工作线程\n");sleep(1);// 手动清理或通过cleanup_pop清理pthread_cleanup_pop(1);  // 执行清理函数return NULL;
}int main() {pthread_t t;pthread_create(&t, NULL, pthread_worker, NULL);pthread_detach(t);sleep(2);return 0;
}

6. 核心结论总结

6.1 资源回收策略对比表

资源类型POSIX pthread_detachC++11 thread::detach说明
线程内核资源✅ 自动回收✅ 自动回收通过底层pthread实现
线程栈空间✅ 自动回收✅ 自动回收操作系统管理
堆内存(原始指针)❌ 手动管理❌ 手动管理程序员责任
堆内存(智能指针)不适用✅ RAII自动管理C++特有机制
文件描述符❌ 手动管理✅ RAII自动管理C++文件流类
互斥锁等同步原语❌ 手动管理✅ RAII自动管理std::lock_guard等

6.2 根本差异的本质

C++11线程分离的优势:

  1. RAII原则:资源获取即初始化,析构时自动释放
  2. 异常安全:异常发生时资源仍然被正确释放
  3. 类型安全:编译时检查资源管理错误
  4. 代码简洁:减少手动资源管理代码

底层事实

  • C++11的std::thread::detach()在Linux上底层调用pthread_detach()
  • 线程系统资源的回收机制完全相同
  • 差异在于应用程序级资源的管理策略
  • C++11通过RAII模式提供了更高级别的资源安全保证

6.3 实际建议

  1. 新项目:优先使用C++11线程,享受RAII带来的资源安全
  2. 现有pthread项目:可以逐步引入RAII包装器
  3. 性能关键:理解底层机制,避免不必要的抽象开销
  4. 资源敏感:C++11提供更安全的资源生命周期管理
http://www.dtcms.com/a/572602.html

相关文章:

  • Kubernetes基础概念和命令
  • 技术干货-MYSQL数据类型详解
  • 备份工具:rsync、Tar、Borg、Veeam 备份与恢复方案
  • 深入 Pinia 工作原理:响应式核心、持久化机制与缓存策略
  • 【前端】动态插入并渲染大量数据的方法-时间分片:使用requestAnimationFrame+DocumentFragment
  • 耶鲁大学Hello Robot研究解读:人类反馈策略的多样性与有效性
  • Unity摄像机鼠标右键旋转功能
  • Spring AI Alibaba文生图实战:从零开始编写AI图片生成Demo
  • 文本编辑器做网站国外设计师
  • 网站多久电子信息工程就业方向
  • 大连网站seo顾问企业开发网站公司
  • 南京网站设计搭建公司网站怎么做rss
  • 外包做网站谷歌seo优化
  • 博物馆网站 建设方案外贸短视频营销
  • 网站如何在360做提交微信开发公司怎么样
  • 广州微网站建设信息设计图案大全
  • 苏州吴中区专业做网站郑州哪里可以做网站
  • cms网站开发毕设简述网站建设的方法
  • 怎样建立网站挣钱网站建设功能选择表
  • 郑州加盟做网站开源程序网站
  • 万维网网站注册网站线下推广怎么做
  • 郑州 网站建设:网站开发页面大小适应屏幕
  • 网络营销网站建设知识南通关键词优化软件
  • 公司网站页脚dedecms手机网站模板
  • 团购网站建设公司深圳营销型网页设计公司
  • 东莞网站建设在哪里wordpress 加载中动画
  • 慈溪市建设局网站h5免费制作平台火蚁
  • 平凉北京网站建设百度店铺免费入驻
  • wordpress termmeta怎么把网站排名优化
  • 服装企业网站内容规划企业网站建设方案教程