1. Linux与C++中的进程与线程基础概念
在Linux系统编程和C++开发中,进程和线程是最核心的并发执行单元。理解它们的本质差异是构建稳定高效程序的基础。
进程是操作系统资源分配的基本单位,每个进程都有独立的地址空间、文件描述符、环境变量等系统资源。在Linux中,我们可以通过ps -ef命令查看当前系统中的所有进程。典型的进程创建方式是通过fork()系统调用,它会创建一个与父进程几乎完全相同的子进程(复制了父进程的内存空间、文件描述符等),然后通常配合exec()系列函数来加载新的程序映像。
线程则是进程内的执行流,共享同一进程的资源(如内存空间、文件描述符等),但拥有独立的栈空间和寄存器状态。在C++11之前,Linux下主要使用POSIX线程(pthread)库,而现代C++则通过<thread>标准库提供了原生支持。线程的轻量级特性使其在需要高并发但资源受限的场景中表现出色。
关键区别:进程间通信(IPC)需要显式机制(如管道、消息队列、共享内存等),而线程间可直接通过共享内存通信,但也因此需要同步机制来避免竞态条件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Linux下的进程操作实战
2.1 进程创建与管理
在Linux系统编程中,最基本的进程创建方式是fork()系统调用。这个看似简单的函数实际上完成了复杂的工作:
cpp复制#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
int main() {
pid_t pid = fork(); // 关键调用:分裂进程
if (pid < 0) {
std::cerr << "Fork failed" << std::endl;
return 1;
}
else if (pid == 0) {
// 子进程代码
std::cout << "Child process, PID: " << getpid()
<< ", Parent PID: " << getppid() << std::endl;
// 通常在这里调用exec系列函数
_exit(0); // 子进程退出
}
else {
// 父进程代码
std::cout << "Parent process, Child PID: " << pid
<< ", My PID: " << getpid() << std::endl;
wait(nullptr); // 等待子进程结束
}
return 0;
}
实际项目中,我们还需要处理以下复杂情况:
- 僵尸进程:子进程退出后父进程未调用
wait(),导致进程描述符残留。可通过信号处理或双重fork解决。 - 孤儿进程:父进程先于子进程退出,子进程被init进程接管。通常无害但需注意资源释放。
- 守护进程:通过
setsid()创建独立会话,脱离终端控制的后台服务进程。
2.2 进程间通信(IPC)机制
Linux提供了丰富的IPC方式,各有适用场景:
| 机制 | 特点 | 适用场景 | C++实现难度 |
|---|---|---|---|
| 管道(Pipe) | 单向字节流,有亲缘关系要求 | 父子进程简单通信 | ★☆☆☆☆ |
| 命名管道(FIFO) | 有名称的管道,无亲缘关系要求 | 任意进程间持久化通信 | ★★☆☆☆ |
| 消息队列 | 结构化的消息传递,内核持久化 | 需要消息分类的通信场景 | ★★★☆☆ |
| 共享内存 | 最高效的方式,需要同步机制配合 | 大数据量频繁交换 | ★★★★☆ |
| 信号量 | 计数器,用于进程同步 | 资源访问控制 | ★★★☆☆ |
| 信号(Signal) | 异步通知机制 | 异常处理和简单事件通知 | ★★★☆☆ |
| Socket | 最通用的方式,支持跨网络通信 | 分布式系统或复杂通信需求 | ★★★★☆ |
共享内存的典型使用模式:
cpp复制#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
int main() {
// 创建共享内存段
int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return 1;
}
// 附加到进程地址空间
char* ptr = (char*)shmat(shmid, nullptr, 0);
if (ptr == (void*)-1) {
perror("shmat");
return 1;
}
// 使用共享内存
sprintf(ptr, "Hello from PID %d", getpid());
// 分离共享内存
shmdt(ptr);
// 实际项目中需要处理同步和清理
return 0;
}
3. C++中的多线程编程
3.1 现代C++线程API
C++11引入了标准线程库,极大简化了跨平台多线程开发:
cpp复制#include <iostream>
#include <thread>
#include <vector>
void worker(int id) {
std::cout << "Thread " << id << " working..." << std::endl;
}
int main() {
std::vector<std::thread> threads;
// 创建多个线程
for (int i = 0; i < 5; ++i) {
threads.emplace_back(worker, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << "All threads completed" << std::endl;
return 0;
}
关键组件:
std::thread:线程对象,支持多种构造函数std::mutex:互斥锁,保护共享数据std::condition_variable:条件变量,线程间通知std::future/std::promise:异步操作结果传递std::async:异步任务启动
3.2 线程同步与死锁避免
多线程编程中最棘手的问题莫过于竞态条件和死锁。以下是一个典型的生产者-消费者模型实现:
cpp复制#include <queue>
#include <mutex>
#include <condition_variable>
template<typename T>
class ThreadSafeQueue {
public:
void push(T value) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push(std::move(value));
lock.unlock();
cond_.notify_one();
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) return false;
value = std::move(queue_.front());
queue_.pop();
return true;
}
void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this]{ return !queue_.empty(); });
value = std::move(queue_.front());
queue_.pop();
}
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cond_;
};
死锁预防的黄金法则:
- 固定锁的获取顺序(如总是先获取锁A再获取锁B)
- 使用
std::lock()同时获取多个锁 - 避免在持有锁时调用用户代码
- 使用RAII管理锁的生命周期(如
std::lock_guard)
4. 高级话题与性能优化
4.1 线程池实现模式
频繁创建销毁线程开销大,线程池是常见优化手段。以下是简约实现:
cpp复制#include <vector>
#include <queue>
#include <thread>
#include <functional>
#include <future>
class ThreadPool {
public:
explicit ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock,
[this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread &worker : workers)
worker.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
4.2 现代C++并发工具
C++17/20引入了更多并发工具:
std::scoped_lock:多锁RAII包装器std::barrier:线程同步屏障(C++20)std::latch/std::counting_semaphore:轻量级同步原语std::jthread:可自动join的线程(C++20)
原子操作示例:
cpp复制#include <atomic>
#include <thread>
std::atomic<int> counter{0};
void increment() {
for (int i = 0; i < 100000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
内存序选择原则:
memory_order_seq_cst:默认选项,保证顺序一致性memory_order_acquire/release:读写操作的同步memory_order_relaxed:仅保证原子性,性能最高
在实际项目中,进程和线程的选择往往取决于多个因素:数据共享需求、容错要求、开发复杂度等。CPU密集型任务可能更适合多进程(避免GIL问题),而IO密集型任务则可能从轻量级线程中获益更多。现代服务器程序通常会结合两者优势,采用多进程+每进程多线程的混合模型。
