400
社区成员
C++11 引入了 std::thread 来实现多线程编程。std::thread 是 C++ 标准库中的线程类,用于启动和管理线程。std::thread 有几个关键的构造函数,包括默认构造函数、初始化构造函数、拷贝构造函数以及移动构造函数。
未初始化
的线程对象。使用此构造函数创建的线程对象必须在之后通过赋值或者移动构造来初始化。#include <iostream>
#include <thread>
void print_message() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
// 使用默认构造函数创建一个线程对象
std::thread t; // t是一个未初始化的线程对象
// 线程创建后必须显式地启动一个线程
t = std::thread(print_message); // 用赋值操作来初始化线程对象
t.join(); // 等待线程完成
return 0;
}
//输出:Hello from thread!
#include <iostream>
#include <thread>
void print_message() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
// 使用初始化构造函数创建并启动线程
std::thread t(print_message); // 直接在构造时传入可调用对象
t.join(); // 等待线程完成
return 0;
}
//输出:Hello from thread!
不可用的
,这是因为线程是无法被复制的。线程对象在创建时会与底层操作系统的线程关联,复制线程对象会导致线程状态不一致
,因此拷贝构造函数被禁用。#include <iostream>
#include <thread>
void print_message() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t1(print_message); // 创建线程 t1
// std::thread t2 = t1; // 错误:无法复制线程对象,因为拷贝构造函数被禁用
t1.join(); // 等待线程 t1 完成
return 0;
}
//编译错误:error: use of deleted function ‘std::thread::thread(const std::thread&)’
移动构造函数
,允许将一个线程对象转移到另一个线程对象。这是必要的,因为线程对象不能被复制,但是可以通过移动语义
将资源(即线程)转移到新的线程对象中。#include <iostream>
#include <thread>
void print_message() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t1(print_message); // 创建线程 t1
// 使用移动构造函数,将 t1 的线程资源转移到 t2
std::thread t2 = std::move(t1); // t1 的资源被转移到 t2,t1 变为空线程
// 注意:t1 现在是一个空线程,不能调用 join() 或者其他线程操作
t2.join(); // 等待线程 t2 完成
return 0;
}
void print_thread_info(int id) {
std::cout << "Thread " << id << " is executing\n";
}
int main() {
// 创建线程对象的容器
std::vectorstd::thread threads;
// 启动5个线程
for (int i = 1; i <= 5; ++i) {
threads.push_back(std::thread(print_thread_info, i));//线程开始执行时,会调用 print_thread_info(i),i 会作为参数传递给该函数。
// 使用 get_id() 获取线程的唯一标识符
std::cout << "Thread " << i << " ID: " << threads[i - 1].get_id() << std::endl;
// 检查线程是否可加入
if (threads[i - 1].joinable()) {
std::cout << "Thread " << i << " is joinable\n";
}
}
// 等待所有线程完成
for (auto& t : threads) {
// 线程加入
if (t.joinable()) {
t.join();
std::cout << "Thread with ID " << t.get_id() << " has finished.\n";
}
}
// 创建并分离一个线程
std::thread detached_thread(print_thread_info, 100);
detached_thread.detach(); // 将线程分离,主线程不会等待它结束
std::cout << "Detached thread is now running independently.\n";
// 确保主线程在程序结束前等待所有线程完成
std::this_thread::sleep_for(std::chrono::seconds(1)); // 给分离线程一些时间运行
return 0;
}
#### 2.线程封装
`封装线程的常见方式是创建一个类来管理线程的生命周期`
ZeroThread 类将负责:
- 启动一个线程来执行某个函数。
- 提供接口来管理线程的生命周期,例如 join 和 detach。
- 防止在销毁对象时忘记处理线程的结束。
```cpp
#include <iostream>
#include <thread>
#include <functional>
#include <atomic>
class ZeroThread {
public:
// 构造函数,接受一个可调用对象(例如函数或 lambda)
template <typename Callable, typename... Args>
explicit ZeroThread(Callable&& func, Args&&... args)
: thread_(std::forward<Callable>(func), std::forward<Args>(args)...), is_joined_(false) {}
// 禁止拷贝构造和拷贝赋值
ZeroThread(const ZeroThread&) = delete;
ZeroThread& operator=(const ZeroThread&) = delete;
//在 C++11 中,= delete 是一个新特性,用于显式禁用某些函数
/*
ZeroThread&:表示返回当前对象的引用。
operator=:定义赋值操作符。
const ZeroThread& other:表示赋值操作符接受一个常量引用,其是另一个 ZeroThread 对象,将被赋值给当前对象。
*/
// 析构函数:如果线程还没有被 join 或 detach,自动 join
~ZeroThread() {
if (thread_.joinable() && !is_joined_) {
thread_.join(); // 确保线程在销毁之前结束
}
}
// join 线程
void join() {
if (thread_.joinable() && !is_joined_) {
thread_.join();
is_joined_ = true;
}
}
// detach 线程
void detach() {
if (thread_.joinable() && !is_joined_) {
thread_.detach();
is_joined_ = true;
}
}
// 获取线程的唯一标识符
std::thread::id get_id() const {
return thread_.get_id();
}
// 判断线程是否可以被 join
bool joinable() const {
return thread_.joinable();
}
private:
std::thread thread_; // 用于存储线程
std::atomic<bool> is_joined_; // 确保线程不会被 join 多次
/*
这是一个原子变量,用于标记线程是否已经被 join 或 detach。原子变量 (std::atomic) 确保它在多线程环境中是安全的,即使多个线程同时访问该变量,也不会引发数据竞争。
原子操作的好处:
std::atomic 提供了线程安全的操作,确保对 is_joined_ 的读取和写入不会发生竞争条件,从而防止多个线程在并发情况下修改该标志。
*/
};
void print_message(int thread_id) {
std::cout << "Thread " << thread_id << " is executing" << std::endl;
}
int main() {
// 创建一个 ZeroThread 实例,启动线程并执行 print_message 函数
ZeroThread t1(print_message, 1);
ZeroThread t2(print_message, 2);
// 使用 join 等待线程完成
t1.join();
t2.join();
return 0;
}
互斥量(mutex)用于在多线程环境下保护共享资源,防止数据竞争
。C++ 标准库提供了多种类型的互斥量,具有不同的特性和功能。
| 类型 | 特性 | 是否支持递归 | 是否带超时功能 |
|--------------------------------|------------------------------------------------|--------------|----------------|
| std::mutex
| 最基本的独占互斥量,不能递归使用 | 否 | 否 |
| std::timed_mutex
| 带超时的独占互斥量,不能递归使用 | 否 | 是 |
| std::recursive_mutex
| 递归互斥量,允许同一线程多次 lock
| 是 | 否 |
| std::recursive_timed_mutex
| 带超时的递归互斥量,允许同一线程多次 lock
| 是 | 是 |
独占锁
,意味着同一时间只能有一个线程持有锁,其他线程必须等待。std::mutex mtx;
void print_numbers(int id) {
mtx.lock();
std::cout << "Thread " << id << " is printing numbers" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}
#### 2. std::timed_mutex - 带超时的独占互斥量,不能递归使用
1. 特性
std::timed_mutex 继承自 std::mutex,与 std::mutex 相似,但它具有 带超时的特性。即,当线程请求锁时,可以指定一个最大等待时间。如果在规定时间内未能获取到锁,try_lock_for() 或 try_lock_until() 会返回 false。
不能递归使用:与 std::mutex 相同,不能多次 lock,否则会导致死锁。
2. 主要成员函数
try_lock_for(std::chrono::milliseconds):尝试获取锁,在指定的时间内等待锁。如果超时,返回 false;否则,成功获取锁并返回 true。
try_lock_until(std::chrono::steady_clock::time_point):尝试在指定的时间点前获取锁,如果在指定时间点前没有获取到锁,则返回 false。
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex tmtx;
void try_lock_for_example(int id) {
if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "Thread " << id << " acquired the lock!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Simulate work
tmtx.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire the lock within timeout." << std::endl;
}
}
int main() {
std::thread t1(try_lock_for_example, 1);
std::thread t2(try_lock_for_example, 2);
t1.join();
t2.join();
return 0;
}
std::recursive_mutex rmtx;
void recursive_function(int id, int count) {
if (count == 0) return;
rmtx.lock();
std::cout << "Thread " << id << " entered recursion level " << count << std::endl;
recursive_function(id, count - 1);
rmtx.unlock();
}
int main() {
std::thread t1(recursive_function, 1, 3);//1 和 3:这两个是参数,它们会被传递给 recursive_function 函数
t1.join();
return 0;
}
#### 4. std::recursive_timed_mutex - 带超时的递归互斥量
1. 特性
std::recursive_timed_mutex 是 std::recursive_mutex 和 std::timed_mutex 的结合,支持 递归锁定 和 带超时的特性。
允许同一线程递归地多次 lock,并且可以设置超时,在指定时间内无法获取锁时返回失败。
2. 主要成员函数
lock():获取锁,允许递归锁定。
unlock():释放锁。
try_lock_for(std::chrono::milliseconds):尝试获取锁,在指定的时间内等待,如果超时返回 false。
try_lock_until(std::chrono::steady_clock::time_point):尝试在指定的时间点前获取锁,如果没有获取到锁则返回 false。
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::recursive_timed_mutex rttmtx;
void try_recursive_lock_for_example(int id, int count) {
if (rttmtx.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "Thread " << id << " acquired the lock!" << std::endl;
if (count > 0) {
try_recursive_lock_for_example(id, count - 1); // Recursive lock
}
rttmtx.unlock();
} else {
std::cout << "Thread " << id << " failed to acquire the lock within timeout." << std::endl;
}
}
int main() {
std::thread t1(try_recursive_lock_for_example, 1, 3);
t1.join();
return 0;
}
相对于手动lock和unlock,我们可以使用RAII(通过类的构造析构)来实现更好的编码方式。
RAII:也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象
std::lock_guard
- 简单的互斥量封装1.特性
std::lock_guard
是一个简单的互斥量管理器,它在构造时自动获取锁,在析构时自动释放锁。lock_guard
管理直到对象销毁。lock_guard
对象的生命周期相匹配时,使用 std::lock_guard
。#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void print_numbers(int id) {
std::lock_guard<std::mutex> lock(mtx); // 自动锁定和解锁
std::cout << "Thread " << id << " is printing numbers" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}
//std::lock_guard 在 print_numbers 函数内部锁住了 mtx,并且在 print_numbers 函数结束时自动解锁。
std::unique_lock
- 更灵活的互斥量管理std::mutex mtx;
void print_numbers(int id) {
std::unique_lockstd::mutex lock(mtx); // 手动锁定互斥量
std::cout << "Thread " << id << " is printing numbers" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
lock.unlock(); // 手动解锁
std::cout << "Thread " << id << " released lock" << std::endl;
}
int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);
t1.join();
t2.join();
return 0;
}
---
### 3. 条件变量
互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下
#### 1.条件变量使用过程:
1. 拥有条件变量的线程获取互斥量;
2. 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
3. 某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。
#### 2.成员函数
| 函数 | 说明 | 适用场景 |
|---------------------|------------------------------------------------------------------|---------------------------------------------------------|
| `wait` | 阻塞当前线程直到条件变量满足或被唤醒。 | 当线程需要等待某个条件(通常需要与循环一起使用)。 |
| `wait_for` | 阻塞当前线程直到条件满足或超时。 | 当需要限制等待时间时,适用于等待超时的场景。 |
| `wait_until` | 阻塞当前线程直到条件满足或达到指定时间点。 | 适用于等待直到某个特定的时间点。 |
| `notify_one` | 唤醒一个等待条件变量的线程。 | 当某个条件满足时,只唤醒一个等待线程。 |
| `notify_all` | 唤醒所有等待条件变量的线程。 | 当某个条件满足且所有等待线程都应继续执行时。 |
##### 1. wait
wait 是最常用的条件变量成员函数,用于使线程等待一个条件成立。它会释放与条件变量关联的互斥量,然后挂起线程的执行,直到其他线程通知它。
1. 语法
```cpp
void wait(std::unique_lock<std::mutex>& lock);
lock:一个已经锁定的 std::unique_lock 对象,表示条件变量等待时释放的互斥量。
当 wait 被调用时,条件变量会释放 lock,并将调用线程挂起,直到其他线程通过 notify_one 或 notify_all 唤醒它。
2. 注意事项:
由于虚假唤醒(spurious wakeups)问题,通常需要将 wait 放在循环中,直到实际的条件成立。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件成立
std::cout << "Thread " << id << " is running" << std::endl;
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all(); // 唤醒所有等待的线程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "Waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
go(); // 唤醒所有线程
for (auto& t : threads) {
t.join();
}
return 0;
}
wait_for 函数用于等待条件满足或者等待指定的超时时间。如果在超时时间内条件不满足,函数会返回,并且条件不会被改变。
template <class Rep, class Period>
std::cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time);
lock:一个已经锁定的 std::unique_lock 对象,表示条件变量等待时释放的互斥量。std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lockstd::mutex lock(mtx);
if (cv.wait_for(lock, std::chrono::seconds(2), []{ return ready; })) {
std::cout << "Thread " << id << " is running" << std::endl;
} else {
std::cout << "Thread " << id << " timed out" << std::endl;
}
}
void go() {
std::unique_lockstd::mutex lock(mtx);
ready = true;
cv.notify_all(); // 唤醒所有线程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "Waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
go(); // 唤醒所有线程
for (auto& t : threads) {
t.join();
}
return 0;
}
##### 3. wait_until
wait_until 函数类似于 wait_for,但是它等待`一个特定的时间点`,而不是持续等待一段时间。
1. 语法
```cpp
template <class Clock, class Duration>
std::cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time);
lock:一个已经锁定的 std::unique_lock 对象。
timeout_time:指定的绝对时间点,通常使用 std::chrono::time_point 类型。
返回值:
std::cv_status:表示条件是否满足。可以是 std::cv_status::no_timeout 或 std::cv_status::timeout。
2. 注意事项:
适用于需要等待直到某个具体时间点的场景。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
if (cv.wait_until(lock, timeout, []{ return ready; })) {
std::cout << "Thread " << id << " is running" << std::endl;
} else {
std::cout << "Thread " << id << " timed out" << std::endl;
}
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all(); // 唤醒所有线程
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "Waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
go(); // 唤醒所有线程
for (auto& t : threads) {
t.join();
}
return 0;
}
notify_one 通知等待条件变量的 一个线程,使其从 wait、wait_for 或 wait_until 中返回。
语法:
void notify_one();
注意事项:
notify_one 唤醒一个等待中的线程。如果没有线程在等待,它不会有任何效果。
cv.notify_one(); // 唤醒一个等待的线程
notify_all 通知所有等待条件变量的线程,使它们都从 wait、wait_for 或 wait_until 中返回。
语法:
void notify_all();
注意事项:
notify_all 唤醒所有等待的线程。通常用于当某个条件已变更且所有等待的线程都应当继续执行时。
cv.notify_all(); // 唤醒所有等待的线程
T load(memory_order order = memory_order_seq_cst) const;
void store(T desired, memory_order order = memory_order_seq_cst);
T exchange(T desired, memory_order order = memory_order_seq_cst);
如果原子变量的当前值与期望值相等,则将其设置为新值。此操作原子地执行。
bool compare_exchange_strong(T& expected, T desired, memory_order success_order = memory_order_seq_cst, memory_order failure_order = memory_order_seq_cst);
原子操作通常会对内存操作的顺序产生影响。C++ 提供了不同的内存顺序控制,以便优化多线程程序的执行。
std::atomic counter(0); // 定义一个原子变量
void increment() {
for (int i = 0; i < 1000; ++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.load(std::memory_order_relaxed) << std::endl; // 输出原子变量的值
return 0;
}
---
### 5. 异步操作
异步操作可以通过 std::future、std::async、std::packaged_task 和 std::promise 来实现。这些工具允许我们在`不同的线程间`进行`数据传递、任务分发及结果获取`,而不需要显式的线程管理。
| 类/函数 | 说明 | 使用场景 |
|----------------------|--------------------------------------------------------------------|----------------------------------|
| `std::future` | 用于获取异步任务的结果,提供 `get()` 和 `wait()` 等成员函数。 | 异步任务的结果获取。 |
| `std::async` | 启动异步任务并返回一个 `std::future`。可以选择任务在新线程执行。 | 异步任务的启动,适合简单的任务执行。|
| `std::packaged_task` | 将可调用对象包装成任务,并通过 `get_future()` 返回 `std::future`。 | 需要在特定线程中执行的异步任务。 |
| `std::promise` | 设置异步任务的结果,通过 `get_future()` 返回 `std::future`。 | 异步任务的结果设置,提供线程间的通信。|
#### 1. std::future
std::future 用于`获取异步操作的结果`。它可以与 std::async、std::promise 或 std::packaged_task 配合使用来获取异步操作的返回值。
1. 成员函数:
- get():阻塞调用,直到异步操作完成并返回结果。
- valid():检查 std::future 是否有效,表示是否可以获取结果。
- wait():阻塞当前线程,直到异步任务完成。
```cpp
#include <iostream>
#include <future>
#include <chrono>
int do_work() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}
int main() {
std::future<int> result = std::async(std::launch::async, do_work);
std::cout << "Doing other work..." << std::endl;
// 等待异步任务结果
int value = result.get(); // 阻塞直到 do_work 完成
std::cout << "Result: " << value << std::endl;
return 0;
}
//std::async 启动一个异步任务,返回一个 std::future 对象。
//使用 result.get() 获取异步任务的返回值,会阻塞当前线程,直到 do_work 完成。
std::async 用于启动一个异步任务,返回一个 std::future,通过它可以获取异步任务的结果。
std::future<T> std::async(std::launch policy, Callable&& f, Args&&... args);
int add(int a, int b) {
return a + b;
}
int main() {
std::future result = std::async(std::launch::async, add, 3, 4);
std::cout << "Waiting for the result..." << std::endl;
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
//使用 std::async 启动一个异步任务,该任务计算 add(3, 4),并返回结果。
//调用 result.get() 时,主线程会阻塞,直到异步任务完成。
#### 3. std::packaged_task
std::packaged_task 将一个可调用对象(如函数、lambda 表达式等)包装成一个任务,并返回一个 std::future 结果对象。你可以将 std::packaged_task 与 std::thread 一起使用。
1. 语法:
```cpp
std::packaged_task<T()> task(f);
std::future<T> get_future();
task():传入一个可调用对象 f,将其包装成任务。
get_future():获取与任务关联的 std::future 对象。
#include <iostream>
#include <future>
#include <thread>
int multiply(int x, int y) {
return x * y;
}
int main() {
std::packaged_task<int(int, int)> task(multiply); // 包装任务
std::future<int> result = task.get_future(); // 获取 future
// 在新线程中执行任务
std::thread t(std::move(task), 4, 5);
t.join(); // 等待线程完成
std::cout << "Result: " << result.get() << std::endl; // 获取异步任务的结果
return 0;
}
std::promise 用于设置异步操作的结果,并与 std::future 配合使用。std::promise 会在某个线程中设置结果,而 std::future 则在另一个线程中获取结果。
std::promise<T> promise;
std::future<T> promise.get_future();
get_future():返回与 promise 关联的 std::future 对象,用于获取结果。void set_value(std::promise& prom) {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value(10); // 设置结果
}
int main() {
std::promise prom;
std::future result = prom.get_future(); // 获取 future
std::thread t(set_value, std::ref(prom)); // 在新线程中设置结果
t.join();
std::cout << "Result: " << result.get() << std::endl; // 获取结果
return 0;
}
/*
std::promise 在 set_value 函数中设置结果。
std::future 用于从主线程获取结果。
std::promise 和 std::future 之间建立了一个数据传递通道。
*/
## 2. function和bind
### 1. std::function
std::function 是 C++11 引入的模板类,它可以包装任何可以调用的目标,包括普通函数、Lambda 表达式、函数指针、成员函数指针、以及其他的可调用对象。它提供了一种统一的方式来存储和调用函数。
#### 1.特性:
1. 可存储任何可调用对象:可以存储普通函数、函数指针、Lambda 表达式、成员函数指针等。
2. 类型安全:通过模板保证了类型安全。
3. 多态性:能够支持不同类型的可调用对象,使得函数可以作为参数或返回值传递。
#### 2. 语法:
```cpp
std::function<ReturnType(Args...)> function_name;
operator():调用存储的可调用对象。
get():获取存储的可调用对象。
#include <iostream>
#include <functional>
void hello() {
std::cout << "Hello, world!" << std::endl;
}
int add(int a, int b) {
return a + b;
}
int main() {
// 存储普通函数
std::function<void()> func1 = hello;
func1(); // 调用函数
// 存储带参数的函数
std::function<int(int, int)> func2 = add;
std::cout << "Sum: " << func2(3, 4) << std::endl;
// 存储 Lambda 表达式
std::function<int(int, int)> func3 = [](int a, int b) { return a * b; };
std::cout << "Product: " << func3(3, 4) << std::endl;
return 0;
}
std::bind 用于绑定函数
或函数对象的参数
,使得函数的某些参数预先固定,从而创建一个新的可调用对象。它通常与 std::function 一起使用,使得函数更灵活。
预先绑定参数:允许部分指定参数值,返回一个新的可调用对象。
创建适配器:可以将不匹配的函数签名转换为所需的签名。
函数适配:通过 std::bind,可以适配成员函数、全局函数等。
auto bound_function = std::bind(Function, arg1, arg2, ..., argN);
Function:需要绑定的可调用对象(函数、成员函数、Lambda 等)。
arg1, arg2, ..., argN:部分或全部参数,可以使用 std::placeholders 来指定占位符。
常用占位符:
std::placeholders::_1, _2, ..., _N:表示绑定时未指定的参数。
#include <iostream>
#include <functional>
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 使用 std::bind 绑定部分参数
auto add_5 = std::bind(add, 5, std::placeholders::_1); // 绑定第一个参数为 5
std::cout << "5 + 10 = " << add_5(10) << std::endl; // 调用时只需要提供第二个参数
// 使用 std::bind 绑定全部参数
auto multiply_2 = std::bind(multiply, std::placeholders::_1, 2); // 绑定第二个参数为 2
std::cout << "6 * 2 = " << multiply_2(6) << std::endl;
return 0;
}
在 C++11 中,引入了可变模板参数(Variadic Templates),这使得模板能够接受不定数量的模板参数。对于这些可变参数,可以通过递归展开和逗号表达式展开来进行处理。
可变模板参数允许我们在模板定义时不指定参数的数量,参数的个数可以在实例化时指定。这为 C++ 提供了强大的泛型编程能力。
template <typename... Args> // Args 为一个参数包
void func(Args... args) {
// 函数体
}
Args... 是一个参数包,代表了一组类型参数。template <typename... Args>
void print(Args... args) {
// 通过折叠表达式展开参数包
(std::cout << ... << args) << std::endl;
}
int main() {
print(1, 2.5, "Hello", 'a'); // 输出: 12.5Helloa
print("C++", "is", "awesome"); // 输出: C++isawesome
return 0;
}
### 2. 展开参数包
#### 1. 递归方式展开参数包
递归展开参数包的方式是通过递归模板函数来逐个处理每个参数,直到参数包为空为止。这是一种比较经典的展开方式。
```cpp
#include <iostream>
// 基本情况,参数包为空时停止递归
void print() {
std::cout << "End of recursion" << std::endl;
}
// 递归展开参数包
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归处理剩余的参数
}
int main() {
print(1, 2.5, "Hello", 'a');
return 0;
}
//输出:1 2.5 Hello a End of recursion
/*
print() 是基本递归函数,它作为递归的终止条件。
print(T first, Args... rest) 展开了参数包,打印当前的第一个参数 first,并递归地处理剩下的参数 rest...。
递归逐渐缩小参数包,直到参数包为空,递归终止。
*/
逗号表达式(Comma Expressions)可以用于在一个表达式中同时处理多个参数。逗号表达式会从左到右依次计算每个参数,并返回最后一个参数的值。在模板中,我们可以利用这一特性来展开参数包。
#include <iostream>
// 使用逗号表达式展开参数包
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 逗号表达式展开
}
int main() {
print(1, 2.5, "Hello", 'a');
return 0;
}
/*
输出:12.5Helloa
通过 (std::cout << ... << args) 使用了 C++17 引入的折叠表达式(fold expression)。
折叠表达式是一种简化的语法,可以直接在多个参数上执行相同的操作,简洁地展开参数包。
*/
template <typename... Args>
void print(Args... args) {
std::cout << (args + ...); // 对所有参数进行加法折叠
}
int main() {
print(1, 2, 3, 4); // 输出 10
return 0;
}
## 4. 异常处理
### 1. 异常处理的基本语法
C++ 的异常处理机制基于 try-catch 块。通过 throw 抛出异常,通过 catch 捕获异常。
```cpp
try {
// 代码块中可能抛出异常
if (/* some condition */) {
throw SomeException(); // 抛出异常
}
} catch (const SomeException& e) {
// 异常处理代码
std::cout << "Exception caught: " << e.what() << std::endl;
} catch (const std::exception& e) {
// 处理其他标准异常
std::cout << "Standard exception: " << e.what() << std::endl;
}
std::exception 是所有标准异常类的基类,它提供了一个 what() 函数来返回异常的描述信息。大部分标准异常类都继承自 std::exception。
#include <iostream>
#include <exception>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "My custom exception!";
}
};
int main() {
try {
throw MyException();
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
C++ 标准库中有多个异常类,它们都继承自 std::exception,每个异常类表示不同类型的错误。
异常类 说明
std::exception 所有标准异常的基类,提供 what() 方法来获取异常信息
std::bad_alloc 表示内存分配失败(通常由 new 抛出)
std::bad_cast 用于类型转换失败,通常在 dynamic_cast 中抛出
std::bad_typeid 用于类型信息查询失败(如不适用于 typeid)
std::out_of_range 当访问容器或数组的越界元素时抛出
std::invalid_argument 表示传给函数的参数无效
std::overflow_error 数值溢出错误
std::underflow_error 数值下溢错误
std::logic_error 逻辑错误,通常是程序的设计或实现错误
std::runtime_error 运行时错误,表示程序在运行时遇到的问题
#include <iostream>
#include <stdexcept>
void test_out_of_range() {
throw std::out_of_range("Out of range error!");
}
int main() {
try {
test_out_of_range();
} catch (const std::out_of_range& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
C++11 引入了 std::exception_ptr,它允许我们捕获当前抛出的异常,并在后续的代码中重新抛出该异常。这对于在异常处理中转发异常或者跨线程传递异常特别有用。
void func() {
try {
throw std::runtime_error("An error occurred in func");
} catch (...) {
// 捕获所有异常,并保存异常指针
std::exception_ptr ptr = std::current_exception();
throw ptr; // 将异常指针重新抛出
}
}
int main() {
try {
func();
} catch (const std::exception_ptr& e) {
try {
std::rethrow_exception(e); // 重新抛出异常
} catch (const std::exception& ex) {
std::cout << "Caught exception: " << ex.what() << std::endl;
}
}
return 0;
}
//在 func() 中,我们首先抛出一个异常,然后通过 std::current_exception() 获取当前抛出的异常。
//在 main() 中,通过 std::rethrow_exception() 重新抛出这个异常,并捕获它进行处理。
## 5.实现线程池
### 1. ZERO_ThreadPool.h — 线程池头文件
```cpp
#ifndef ZERO_THREADPOOL_H
#define ZERO_THREADPOOL_H
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>
class ZERO_ThreadPool {
public:
// 构造函数
ZERO_ThreadPool();
// 初始化线程池,设置线程数
void init(size_t numThreads);
// 启动线程池
void start();
// 提交任务到线程池
template <typename Func, typename... Args>
void exec(Func&& func, Args&&... args);
// 等待所有任务完成,最多等待 `timeoutMs` 毫秒
bool waitForAllDone(int timeoutMs = -1);
// 停止线程池
void stop();
// 析构函数
~ZERO_ThreadPool();
private:
// 工作线程函数
void workerThread();
// 任务队列
std::queue<std::function<void()>> taskQueue;
// 线程池状态变量
std::vector<std::thread> workers;
std::mutex queueMutex;
std::mutex doneMutex;
std::condition_variable condition;
std::condition_variable doneCondition;
std::atomic<bool> stopFlag;
std::atomic<size_t> activeThreads;
size_t threadCount;
};
#endif // ZERO_THREADPOOL_H
#include "ZERO_ThreadPool.h"
// 构造函数
ZERO_ThreadPool::ZERO_ThreadPool() : stopFlag(false), threadCount(0), activeThreads(0) {}
// 初始化线程池,设置线程数
void ZERO_ThreadPool::init(size_t numThreads) {
if (numThreads == 0) {
std::cerr << "Thread pool must have at least one thread." << std::endl;
return;
}
threadCount = numThreads;
}
// 启动线程池
void ZERO_ThreadPool::start() {
assert(threadCount > 0 && "Thread pool not initialized.");
/*
assert 是一种断言宏,它在调试模式下会检查条件是否成立。如果条件为 false,则会打印错误信息并终止程序。
这里检查 threadCount > 0,确保线程池在启动前已正确初始化且线程数大于零。如果 threadCount 小于等于零,将导致程序终止并显示错误信息 "Thread pool not initialized."
*/
for (size_t i = 0; i < threadCount; ++i) {
workers.push_back(std::thread(&ZERO_ThreadPool::workerThread, this));
// 创建一个新线程 函数指针 默认每个线程的执行函数 指向当前对象指针
}
}
// 提交任务到线程池
template <typename Func, typename... Args>
void ZERO_ThreadPool::exec(Func&& func, Args&&... args) {
{
std::lock_guard<std::mutex> lock(queueMutex);
taskQueue.push(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));//完美接发
}
condition.notify_one();
}
// 等待所有任务完成,最多等待 `timeoutMs` 毫秒
bool ZERO_ThreadPool::waitForAllDone(int timeoutMs) {
std::unique_lock<std::mutex> lock(doneMutex);
if (timeoutMs < 0) {
doneCondition.wait(lock, [this]() { return taskQueue.empty() && activeThreads == 0; });
//会让当前线程阻塞,直到条件 taskQueue.empty() && activeThreads == 0 为真
} else {
auto timeout = std::chrono::milliseconds(timeoutMs);
doneCondition.wait_for(lock, timeout, [this]() { return taskQueue.empty() && activeThreads == 0; });
/*
wait_for 是条件变量提供的一个阻塞函数,它会使当前线程阻塞指定的时间(在这里是 timeout),直到以下两个条件之一成立:
1. 条件满足,线程可以继续执行。
2. 超过了指定的超时时间。
第一个参数:lock 是一个已经锁定的 std::unique_lock,它是条件变量的同步基础。
第二个参数:timeout 是一个 std::chrono::milliseconds 类型的超时值,表示线程阻塞的最大时间。
第三个参数:pred 是一个返回布尔值的条件函数。当这个函数返回 true 时,wait_for 会返回并且释放锁。
*/
}
return taskQueue.empty() && activeThreads == 0;
}
// 停止线程池
void ZERO_ThreadPool::stop() {
{
std::lock_guard<std::mutex> lock(queueMutex);
stopFlag = true;
}
condition.notify_all();//唤醒所有等待 condition 条件变量的线程
for (auto& worker : workers) {
if (worker.joinable()) {
worker.join();//join() 是一个阻塞操作,它会阻塞当前线程直到该线程完成执行。调用 join() 可以确保在程序退出前,所有的线程都已经正常完成。
}
}
}
// 析构函数
ZERO_ThreadPool::~ZERO_ThreadPool() {
stop();
}
// 工作线程函数
void ZERO_ThreadPool::workerThread() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this]() { return !taskQueue.empty() || stopFlag; });
if (stopFlag && taskQueue.empty()) {
return;
}
task = std::move(taskQueue.front());
//将队列中队首任务所有权转移到task
taskQueue.pop();
}
++activeThreads;
task();
--activeThreads;
doneCondition.notify_one();
//条件变量,用来同步主线程或其他线程。通知一个线程说明当前线程完成了任务,主线程可以在适当的时候执行相应的操作(如销毁线程、处理结果等)
}
}
// 显示模板函数的定义
template void ZERO_ThreadPool::exec<void(*)(int), int>(void(*)(int), int);
//显式实例化模板函数可以避免编译器在每次调用模板时都进行模板实例化,从而提高编译速度
#include <iostream>
#include "ZERO_ThreadPool.h"
void testFunction(int taskId) {
std::cout << "Task " << taskId << " is being processed by thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟耗时任务
}
int main() {
// 创建线程池
ZERO_ThreadPool tpool;
// 初始化线程池,设置线程数为5
tpool.init(5);
// 启动线程池
tpool.start();
// 将任务丢到线程池中,提交5个任务
for (int i = 0; i < 5; ++i) {
tpool.exec(testFunction, i);
}
// 等待所有任务完成,最多等待1000毫秒
if (tpool.waitForAllDone(1000)) {
std::cout << "All tasks are done." << std::endl;
} else {
std::cout << "Timeout waiting for tasks to complete." << std::endl;
}
// 停止线程池
tpool.stop();
return 0;
}