this_thread
1. 概述
std::this_thread 是 C++11 标准库中一个特殊的命名空间,它提供了一系列用于操作当前线程 的函数。与 std::thread 类不同,std::this_thread 中的函数都是命名空间函数(自由函数),不需要通过线程对象来调用,而是直接作用于调用它的线程本身。
这个命名空间的设计理念非常直观:当你需要让当前线程执行某些操作(比如休眠、让出 CPU、获取自己的 ID)时,直接调用 std::this_thread 中的函数即可,无需持有任何线程对象。
2. 头文件
使用 std::this_thread 需要包含头文件:
cpp
#include <thread>
注意:std::this_thread 和 std::thread 都在同一个头文件 <thread> 中。
3. 核心函数
std::this_thread 命名空间提供了四个核心函数,让我们逐一深入了解。
3.1 get_id() - 获取当前线程 ID
函数签名
cpp
std::thread::id get_id() noexcept;
功能说明
get_id() 返回当前线程的唯一标识符,类型为 std::thread::id。在同一时刻,每个线程都有唯一的 ID,可以用来区分不同的线程。注意:线程结束后,其 ID 可能会被系统重用。
使用场景
- 线程识别:在多线程程序中,通过 ID 来识别和区分不同的线程
- 调试:在日志或调试信息中输出线程 ID,帮助追踪问题
- 线程映射:将线程 ID 作为键,存储线程相关的数据
代码示例
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex coutMutex;
void PrintThreadInfo()
{
std::thread::id threadId = std::this_thread::get_id();
// 使用互斥锁保护输出,避免多线程输出混乱
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "当前线程 ID: " << threadId << std::endl;
}
int main()
{
// 主线程的 ID
std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
// 创建多个线程,每个线程打印自己的 ID
std::thread t1(PrintThreadInfo);
std::thread t2(PrintThreadInfo);
std::thread t3(PrintThreadInfo);
t1.join();
t2.join();
t3.join();
return 0;
}
注意事项
- 默认构造的 thread::id :默认构造的
std::thread::id表示"非线程"(not a thread),它不等于任何有效的线程 ID - 可比较性 :
std::thread::id支持==、!=、<等比较操作,可以用于排序和查找 - 可输出性 :
std::thread::id可以输出到流,但输出格式是实现定义的(通常是数字或十六进制)
3.2 yield() - 让出 CPU 时间片
函数签名
cpp
void yield() noexcept;
功能说明
yield() 是一个提示性 函数,它建议当前线程让出 CPU 时间片,允许其他线程运行。这是一个非阻塞操作,调用后立即返回,不会导致线程休眠。
使用场景
- 忙等待优化 :在自旋锁或忙等待循环中,使用
yield()可以降低 CPU 占用率 - 协作式多任务:在协作式多任务系统中,主动让出 CPU 给其他线程
- 性能优化 :当线程在等待某个条件时,使用
yield()而不是忙等待,可以减少 CPU 资源浪费
代码示例
cpp
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>
std::atomic<bool> ready(false);
std::mutex coutMutex;
void WorkerThread()
{
// 忙等待,但使用 yield() 让出 CPU
while (!ready.load())
{
std::this_thread::yield(); // 让出 CPU,降低 CPU 占用
}
// 使用互斥锁保护输出
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "工作线程 " << std::this_thread::get_id()
<< " 开始工作" << std::endl;
}
int main()
{
std::thread worker(WorkerThread);
// 模拟一些准备工作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 设置就绪标志
ready.store(true);
worker.join();
return 0;
}
注意事项
- 非强制性 :
yield()只是一个提示,操作系统可能忽略这个提示,特别是在单核系统上 - 不保证休眠 :调用
yield()后,线程可能立即继续执行,也可能被调度器暂停 - 性能影响 :在忙等待循环中使用
yield()可以显著降低 CPU 占用率,但可能增加延迟 - 替代方案 :对于需要等待的场景,通常使用
sleep_for()或条件变量更合适
3.3 sleep_for() - 休眠指定时长
函数签名
cpp
template<class Rep, class Period>
void sleep_for(const std::chrono::duration<Rep, Period>& sleepDuration);
功能说明
sleep_for() 让当前线程休眠指定的时间长度。这是一个阻塞操作,线程在休眠期间不会执行任何代码。
参数说明
sleepDuration:休眠的时长,类型为std::chrono::duration,可以使用std::chrono::milliseconds、std::chrono::seconds等
使用场景
- 定时任务:实现定时执行某些操作
- 延迟执行:在操作之间添加延迟
- 节流控制:限制某些操作的执行频率
- 测试和演示:在示例代码中模拟耗时操作
代码示例
cpp
#include <iostream>
#include <thread>
#include <chrono>
int main()
{
std::cout << "开始执行..." << std::endl;
// 休眠 1 秒
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "1 秒后" << std::endl;
// 休眠 500 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "再 500 毫秒后" << std::endl;
// 使用不同的时间单位
std::this_thread::sleep_for(std::chrono::microseconds(100000)); // 0.1 秒
std::cout << "再 0.1 秒后" << std::endl;
return 0;
}
注意事项
- 实际休眠时间 :实际休眠时间可能略长于指定的时间,因为线程调度、系统负载等因素
- 最小精度:休眠的最小精度取决于操作系统的时钟精度,通常为毫秒级
- 可中断性:在某些系统上,休眠可能被信号中断
- 异常安全 :在 C++11 中,
sleep_for()可能抛出异常(例如时钟操作失败时);在 C++14 及以后版本中,sleep_for()被标记为noexcept,不会抛出异常
3.4 sleep_until() - 休眠到指定时间点
函数签名
cpp
template<class Clock, class Duration>
void sleep_until(const std::chrono::time_point<Clock, Duration>& sleepTime);
功能说明
sleep_until() 让当前线程休眠到指定的时间点。与 sleep_for() 不同,sleep_until() 是基于绝对时间 的,而 sleep_for() 是基于相对时间的。
参数说明
sleepTime:目标时间点,类型为std::chrono::time_point
使用场景
- 定时执行:在指定的时间点执行某个操作
- 周期性任务:实现精确的周期性执行(比如每 1 秒执行一次)
- 截止时间控制:在某个截止时间之前执行操作
代码示例
cpp
#include <iostream>
#include <thread>
#include <chrono>
void PeriodicTask()
{
// 获取当前时间
auto startTime = std::chrono::steady_clock::now();
for (int i = 0; i < 5; ++i)
{
// 计算下一次执行的时间点(每秒执行一次)
auto nextTime = startTime + std::chrono::seconds(i + 1);
// 休眠到指定时间点
std::this_thread::sleep_until(nextTime);
std::cout << "第 " << (i + 1) << " 次执行,时间: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime).count()
<< " 毫秒" << std::endl;
}
}
int main()
{
PeriodicTask();
return 0;
}
sleep_for() vs sleep_until()
| 特性 | sleep_for() | sleep_until() |
|---|---|---|
| 时间类型 | 相对时间(时长) | 绝对时间(时间点) |
| 参数类型 | std::chrono::duration |
std::chrono::time_point |
| 适用场景 | 简单的延迟 | 精确的定时执行 |
| 周期性任务 | 可能累积误差 | 更精确,不累积误差 |
代码示例:对比两种方式
cpp
#include <iostream>
#include <thread>
#include <chrono>
// 方式1:使用 sleep_for() - 可能累积误差
void PeriodicTaskWithSleepFor()
{
for (int i = 0; i < 5; ++i)
{
std::cout << "sleep_for: 第 " << (i + 1) << " 次执行" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// 注意:如果循环体执行需要时间,会导致实际间隔 > 1 秒
}
}
// 方式2:使用 sleep_until() - 更精确
void PeriodicTaskWithSleepUntil()
{
auto startTime = std::chrono::steady_clock::now();
for (int i = 0; i < 5; ++i)
{
auto nextTime = startTime + std::chrono::seconds(i + 1);
std::this_thread::sleep_until(nextTime);
std::cout << "sleep_until: 第 " << (i + 1) << " 次执行" << std::endl;
// 即使循环体执行需要时间,下一次执行也会在正确的时间点
}
}
int main()
{
std::cout << "=== 使用 sleep_for() ===" << std::endl;
PeriodicTaskWithSleepFor();
std::cout << "\n=== 使用 sleep_until() ===" << std::endl;
PeriodicTaskWithSleepUntil();
return 0;
}
注意事项
- 时钟类型 :
sleep_until()使用的时钟类型会影响行为std::chrono::system_clock:系统时钟,可能被调整(比如 NTP 同步)std::chrono::steady_clock:单调时钟,不会倒退,适合测量时间间隔
- 时间调整 :如果使用
system_clock,系统时间调整可能导致意外行为 - 已过期时间:如果指定的时间点已经过去,函数会立即返回
- 异常安全 :在 C++11 中,
sleep_until()可能抛出异常(例如时钟操作失败时);在 C++14 及以后版本中,sleep_until()被标记为noexcept,不会抛出异常
4. 实际应用示例
4.1 线程池中的线程管理
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
#include <mutex>
class SimpleWorkerPool
{
public:
SimpleWorkerPool(size_t workerCount)
{
for (size_t i = 0; i < workerCount; ++i)
{
workers.emplace_back([this, i]()
{
// 每个工作线程的 ID
std::thread::id workerId = std::this_thread::get_id();
while (!stopFlag.load())
{
// 模拟工作
{
std::lock_guard<std::mutex> lock(mtx);
std::cout << "工作线程 " << workerId
<< " 正在工作..." << std::endl;
}
// 工作完成后,休眠一段时间
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::lock_guard<std::mutex> lock(mtx);
std::cout << "工作线程 " << workerId << " 退出" << std::endl;
});
}
}
~SimpleWorkerPool()
{
stopFlag.store(true);
for (auto& worker : workers)
{
if (worker.joinable())
{
worker.join();
}
}
}
private:
std::vector<std::thread> workers;
std::atomic<bool> stopFlag{false};
std::mutex mtx;
};
int main()
{
SimpleWorkerPool pool(3);
// 主线程休眠 1 秒,让工作线程运行
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
4.2 精确的定时任务
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <functional>
class Timer
{
public:
// 在指定时间点执行任务
static void ExecuteAt(const std::chrono::steady_clock::time_point& timePoint,
std::function<void()> task)
{
std::this_thread::sleep_until(timePoint);
task();
}
// 延迟指定时间后执行任务
static void ExecuteAfter(const std::chrono::milliseconds& delay,
std::function<void()> task)
{
std::this_thread::sleep_for(delay);
task();
}
};
int main()
{
auto startTime = std::chrono::steady_clock::now();
// 1 秒后执行
Timer::ExecuteAfter(std::chrono::seconds(1), []()
{
std::cout << "1 秒后执行的任务" << std::endl;
});
// 在指定时间点执行
auto targetTime = startTime + std::chrono::seconds(2);
Timer::ExecuteAt(targetTime, []()
{
std::cout << "在指定时间点执行的任务" << std::endl;
});
return 0;
}
4.3 节流控制(Rate Limiting)
什么是节流控制?
节流控制(Rate Limiting)是一种限制操作执行频率的机制。它确保在给定的时间窗口内,操作执行的次数不超过指定的限制。这在很多场景中都非常有用,比如:
- API 调用限制:防止对服务器发送过多请求,避免触发限流或导致服务器过载
- 资源保护:控制对共享资源的访问频率,避免资源耗尽
- 网络请求:限制网络请求的频率,避免网络拥塞
- 日志记录:控制日志输出的频率,避免日志文件过大
工作原理
节流控制的核心思想是:记录上次操作的时间,如果距离上次操作的时间间隔小于最小间隔,则休眠直到达到最小间隔。
具体实现步骤:
- 初始化:设置允许的请求频率(如每秒 10 次),计算最小时间间隔(1000 毫秒 / 10 = 100 毫秒)
- 记录时间:保存上次操作的时间点
- 检查间隔:每次操作前,计算距离上次操作的时间间隔
- 等待调整 :如果间隔小于最小间隔,使用
sleep_for()休眠剩余时间 - 更新时间:操作完成后,更新上次操作的时间点
代码示例
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
class RateLimiter
{
public:
explicit RateLimiter(int requestsPerSecond)
: interval(1000 / requestsPerSecond) // 注意:实际应用中需要检查 requestsPerSecond > 0
, lastTime(std::chrono::steady_clock::now())
{
}
void WaitIfNeeded()
{
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastTime);
if (elapsed < interval)
{
std::this_thread::sleep_for(interval - elapsed);
}
lastTime = std::chrono::steady_clock::now();
}
private:
std::chrono::milliseconds interval;
std::chrono::steady_clock::time_point lastTime;
};
int main()
{
RateLimiter limiter(10); // 限制为每秒 10 次请求
for (int i = 0; i < 20; ++i)
{
limiter.WaitIfNeeded();
std::cout << "请求 " << (i + 1) << std::endl;
}
return 0;
}
代码详解
让我们逐步分析这个 RateLimiter 类的实现:
1. 构造函数
cpp
explicit RateLimiter(int requestsPerSecond)
: interval(1000 / requestsPerSecond) // 计算最小时间间隔(毫秒)
, lastTime(std::chrono::steady_clock::now()) // 记录当前时间作为起始时间
{
}
interval:计算最小时间间隔。例如,如果requestsPerSecond = 10,则interval = 100毫秒,意味着每两次请求之间至少间隔 100 毫秒lastTime:记录上次操作的时间点,初始化为当前时间
2. WaitIfNeeded() 方法
cpp
void WaitIfNeeded()
{
// 1. 获取当前时间
auto now = std::chrono::steady_clock::now();
// 2. 计算距离上次操作的时间间隔
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastTime);
// 3. 如果时间间隔小于最小间隔,则休眠剩余时间
if (elapsed < interval)
{
std::this_thread::sleep_for(interval - elapsed);
}
// 4. 更新上次操作的时间点
lastTime = std::chrono::steady_clock::now();
}
工作流程示例:
假设 RateLimiter(10)(每秒 10 次,即每 100 毫秒一次):
- 第 1 次调用 :
elapsed = 0(刚初始化),0 < 100,休眠 100 毫秒 - 第 2 次调用 :如果距离第 1 次已经过了 50 毫秒,
50 < 100,休眠 50 毫秒 - 第 3 次调用 :如果距离第 2 次已经过了 120 毫秒,
120 >= 100,不休眠,直接执行
使用场景示例
场景 1:API 请求限制
cpp
RateLimiter apiLimiter(5); // 限制为每秒 5 次 API 调用
for (int i = 0; i < 100; ++i)
{
apiLimiter.WaitIfNeeded(); // 确保不超过频率限制
// 执行 API 调用(示例代码,实际使用时替换为真实的 API 调用函数)
// MakeApiCall();
}
场景 2:日志输出控制
cpp
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
RateLimiter logLimiter(1); // 限制为每秒 1 条日志
void LogMessage(const std::string& msg)
{
logLimiter.WaitIfNeeded(); // 控制日志输出频率
std::cout << msg << std::endl;
}
注意事项
- 线程安全 :当前的实现不是线程安全的 。如果多个线程同时调用
WaitIfNeeded(),需要使用互斥锁保护 - 精度问题:使用毫秒级精度,对于需要更高精度的场景,可以使用微秒或纳秒
- 边界检查 :构造函数中需要检查
requestsPerSecond > 0,避免除零错误 - 时钟选择 :使用
steady_clock而不是system_clock,因为steady_clock是单调的,不会因为系统时间调整而受影响
线程安全版本(可选)
如果需要多线程环境下的节流控制,可以这样改进:
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
class ThreadSafeRateLimiter
{
public:
explicit ThreadSafeRateLimiter(int requestsPerSecond)
: interval(1000 / requestsPerSecond)
, lastTime(std::chrono::steady_clock::now())
{
}
void WaitIfNeeded()
{
std::lock_guard<std::mutex> lock(mtx); // 加锁保护
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastTime);
if (elapsed < interval)
{
std::this_thread::sleep_for(interval - elapsed);
}
lastTime = std::chrono::steady_clock::now();
}
private:
std::chrono::milliseconds interval;
std::chrono::steady_clock::time_point lastTime;
std::mutex mtx; // 互斥锁保护共享数据
};
5. 常见错误和注意事项
5.1 不要在主线程中过度使用 sleep
cpp
#include <thread>
#include <chrono>
// ❌ 错误示例:在主线程中长时间休眠
int main()
{
std::thread worker([]()
{
// 工作线程执行任务
});
std::this_thread::sleep_for(std::chrono::seconds(10)); // 主线程休眠
// 问题:如果工作线程提前完成,主线程仍然会等待 10 秒
worker.join();
return 0;
}
// ✅ 正确示例:使用 join() 等待线程完成
int main()
{
std::thread worker([]()
{
// 工作线程执行任务
});
worker.join(); // 等待工作线程完成,无论需要多长时间
return 0;
}
5.2 sleep_for() 的实际休眠时间可能不精确
cpp
#include <iostream>
#include <thread>
#include <chrono>
void TestSleepPrecision()
{
auto start = std::chrono::steady_clock::now();
// 请求休眠 10 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(10));
auto end = std::chrono::steady_clock::now();
auto actual = std::chrono::duration_cast<std::chrono::microseconds>(
end - start).count();
std::cout << "请求休眠: 10 毫秒" << std::endl;
std::cout << "实际休眠: " << actual << " 微秒" << std::endl;
// 实际休眠时间可能略大于 10 毫秒
}
5.3 使用正确的时钟类型
cpp
#include <iostream>
#include <thread>
#include <chrono>
void DemonstrateClockTypes()
{
// ❌ 错误:使用 system_clock 进行时间间隔测量
// system_clock 可能被调整(比如 NTP 同步),不适合测量时间间隔
auto systemStart = std::chrono::system_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1));
auto systemEnd = std::chrono::system_clock::now();
// ✅ 正确:使用 steady_clock 进行时间间隔测量
// steady_clock 是单调的,不会倒退,适合测量时间间隔
auto steadyStart = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1));
auto steadyEnd = std::chrono::steady_clock::now();
auto systemDuration = std::chrono::duration_cast<std::chrono::milliseconds>(
systemEnd - systemStart).count();
auto steadyDuration = std::chrono::duration_cast<std::chrono::milliseconds>(
steadyEnd - steadyStart).count();
std::cout << "system_clock 测量: " << systemDuration << " 毫秒" << std::endl;
std::cout << "steady_clock 测量: " << steadyDuration << " 毫秒" << std::endl;
}
6. 与 std::thread 的配合使用
std::this_thread 和 std::thread 是互补的关系:
- std::thread:用于创建和管理其他线程
- std::this_thread:用于操作当前线程本身
cpp
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
std::mutex coutMutex;
void WorkerFunction(int workerId)
{
// 使用 this_thread 获取当前线程 ID
std::thread::id currentId = std::this_thread::get_id();
{
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "工作线程 " << workerId
<< " (ID: " << currentId << ") 开始工作" << std::endl;
}
// 模拟工作
for (int i = 0; i < 3; ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "工作线程 " << workerId << " 完成步骤 " << (i + 1) << std::endl;
}
}
{
std::lock_guard<std::mutex> lock(coutMutex);
std::cout << "工作线程 " << workerId << " 完成" << std::endl;
}
}
int main()
{
std::cout << "主线程 ID: " << std::this_thread::get_id() << std::endl;
// 使用 std::thread 创建多个工作线程
std::vector<std::thread> workers;
for (int i = 0; i < 3; ++i)
{
workers.emplace_back(WorkerFunction, i + 1);
}
// 主线程等待所有工作线程完成
for (auto& worker : workers)
{
worker.join();
}
std::cout << "所有工作线程完成" << std::endl;
return 0;
}
7. 总结
std::this_thread 命名空间提供了四个核心函数,用于操作当前线程:
- get_id():获取当前线程的唯一标识符
- yield():让出 CPU 时间片(提示性操作)
- sleep_for():休眠指定的时间长度(相对时间)
- sleep_until():休眠到指定的时间点(绝对时间)
这些函数在多线程编程中非常有用,可以帮助我们:
- 识别和区分不同的线程
- 控制线程的执行时机
- 实现定时任务和周期性操作
- 优化 CPU 资源使用
记住:std::this_thread 是操作当前线程 的工具,而 std::thread 是管理其他线程的工具,两者配合使用可以构建强大的多线程应用。