好的,没问题。
各位观众,各位朋友,各位优秀的程序员们,欢迎来到今天的C++技术分享会。今天我们要聊聊一个非常有趣,而且在多线程编程中非常重要的模块:std::this_thread
。它就像线程世界里的身份证和闹钟,让我们能知道“我是谁”和“我该睡多久”。
开场白:线程的自我认知
想象一下,你是一位演员,站在一个巨大的舞台上,周围还有很多和你一样的演员。每个人都在忙着自己的角色,完成各自的任务。在多线程编程中,每个线程就像一位演员,而 std::this_thread
就好比是演员的身份牌,能让你知道自己是几号演员,以及什么时候该休息一下。
std::this_thread::get_id()
:我是谁?
首先,我们来认识一下 std::this_thread::get_id()
。这个函数就像一个身份识别器,它可以告诉你当前线程的 ID。这个 ID 是 std::thread::id
类型的,它可以用来唯一标识一个线程。
#include <iostream>
#include <thread>
#include <chrono>
void print_thread_id() {
std::thread::id id = std::this_thread::get_id();
std::cout << "线程 ID: " << id << std::endl;
}
int main() {
std::thread t1(print_thread_id);
std::thread t2(print_thread_id);
print_thread_id(); // 主线程的 ID
t1.join();
t2.join();
return 0;
}
这段代码创建了两个线程 t1
和 t2
,每个线程都执行 print_thread_id()
函数,这个函数会打印出当前线程的 ID。主线程也执行 print_thread_id()
函数。运行结果可能会是这样的(每次运行结果可能不同,因为线程的调度是由操作系统决定的):
线程 ID: 140735611271936
线程 ID: 140735602870016
线程 ID: 140735619673856
可以看到,每个线程都有一个唯一的 ID。注意,std::thread::id
本身没有提供直接比较大小的操作,但它重载了 ==
和 !=
运算符,可以用来判断两个线程 ID 是否相等。
std::thread::id
的用法:
- 线程唯一标识: 这是最基本的功能。你可以用它来区分不同的线程。
- 线程池管理: 在线程池中,你可以用线程 ID 来管理线程。
- 调试: 在调试多线程程序时,线程 ID 可以帮助你定位问题。
进阶:比较线程ID
虽然 std::thread::id
不能直接比较大小,但它提供了比较运算符,并且可以转换成可以比较的值。
#include <iostream>
#include <thread>
#include <algorithm>
#include <vector>
int main() {
std::vector<std::thread::id> thread_ids;
std::thread t1([&]{ thread_ids.push_back(std::this_thread::get_id()); });
std::thread t2([&]{ thread_ids.push_back(std::this_thread::get_id()); });
t1.join();
t2.join();
// 排序线程ID (需要C++20及以上,或者自己实现比较函数)
std::sort(thread_ids.begin(), thread_ids.end());
std::cout << "Thread IDs:" << std::endl;
for (const auto& id : thread_ids) {
std::cout << id << std::endl;
}
return 0;
}
这个例子展示了如何获取多个线程的ID,并将其存储在一个容器中。 虽然 std::thread::id
没有提供直接的排序方法,但标准库中的算法通常可以适用于比较。在C++20及以上,可以直接使用比较运算符进行排序。在更早的版本中,可能需要自定义比较函数或者使用其他方式将std::thread::id
转换为可比较的类型。
std::this_thread::sleep_for()
和 std::this_thread::sleep_until()
:该睡多久?
接下来,我们来学习一下线程的睡眠操作。std::this_thread
提供了两个函数:std::this_thread::sleep_for()
和 std::this_thread::sleep_until()
,它们可以让当前线程暂停执行一段时间。
std::this_thread::sleep_for()
: 睡多久,我说得算。这个函数接受一个std::chrono::duration
类型的参数,表示线程应该睡眠的时间长度。std::this_thread::sleep_until()
: 睡到什么时候,我来决定。这个函数接受一个std::chrono::time_point
类型的参数,表示线程应该睡眠到指定的时间点。
#include <iostream>
#include <thread>
#include <chrono>
void do_something() {
std::cout << "开始执行任务..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3)); // 睡眠 3 秒
std::cout << "任务执行完毕!" << std::endl;
}
int main() {
std::cout << "程序开始..." << std::endl;
std::thread t(do_something);
t.join();
std::cout << "程序结束!" << std::endl;
return 0;
}
这段代码创建了一个线程 t
,它会执行 do_something()
函数。do_something()
函数会先打印 "开始执行任务…",然后睡眠 3 秒,最后打印 "任务执行完毕!"。主线程会等待 t
线程执行完毕。运行结果如下:
程序开始...
开始执行任务...
(暂停 3 秒)
任务执行完毕!
程序结束!
再来看一个 sleep_until()
的例子:
#include <iostream>
#include <thread>
#include <chrono>
#include <ctime>
int main() {
std::cout << "程序开始..." << std::endl;
auto now = std::chrono::system_clock::now();
auto ten_seconds_later = now + std::chrono::seconds(10);
std::cout << "当前时间: " << std::ctime(nullptr) << std::endl; // 获取当前时间并转换为可读字符串
std::cout << "睡眠到 10 秒后..." << std::endl;
std::this_thread::sleep_until(ten_seconds_later);
std::cout << "醒来啦!当前时间: " << std::ctime(nullptr) << std::endl;
std::cout << "程序结束!" << std::endl;
return 0;
}
这个例子会睡眠到 10 秒后的时间点。运行结果会显示睡眠前后的时间。
sleep_for()
和 sleep_until()
的应用场景:
- 控制任务执行频率: 比如,你需要每隔一段时间执行一次某个任务,就可以使用
sleep_for()
。 - 等待事件发生: 比如,你需要等待某个条件满足,可以使用
sleep_until()
,并定期检查条件是否满足。 - 模拟耗时操作: 在测试或者演示时,可以使用
sleep_for()
模拟耗时操作。
注意事项:
- 精度问题:
sleep_for()
和sleep_until()
的精度取决于操作系统和硬件。实际睡眠时间可能比指定的时间略长。 - 信号中断: 线程在睡眠期间可能会被信号中断。如果需要处理信号,需要使用
pthread_sigmask()
函数屏蔽信号。 - 不要在关键代码中使用: 避免在关键代码中使用
sleep_for()
和sleep_until()
,因为它们会阻塞线程,影响程序的性能。
结合条件变量:更优雅的等待方式
虽然 sleep_for()
和 sleep_until()
可以让线程睡眠,但它们是忙等待(busy-waiting),会浪费 CPU 资源。更优雅的等待方式是使用条件变量(condition variable)。条件变量可以让线程在等待某个条件满足时进入睡眠状态,并在条件满足时被唤醒。
#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> lck(mtx);
while (!ready) {
cv.wait(lck); // 等待 ready 变为 true
}
std::cout << "线程 " << id << ":线程 ID = " << std::this_thread::get_id() << std::endl;
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true;
cv.notify_all(); // 唤醒所有等待的线程
}
int main() {
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 个线程准备就绪。开始 go!" << std::endl;
go(); // go!
for (auto& th : threads)
th.join();
return 0;
}
在这个例子中,10 个线程都在等待 ready
变为 true
。当 go()
函数被调用时,ready
变为 true
,并且所有等待的线程都被唤醒。这样就避免了忙等待,提高了程序的效率。
std::this_thread::yield()
:给其他线程让路
除了睡眠,std::this_thread
还提供了一个 yield()
函数,它可以让当前线程主动放弃 CPU 的使用权,让其他线程有机会执行。
#include <iostream>
#include <thread>
void do_something() {
for (int i = 0; i < 1000000; ++i) {
if (i % 1000 == 0) {
std::this_thread::yield(); // 让出 CPU
}
}
std::cout << "任务执行完毕!" << std::endl;
}
int main() {
std::thread t1(do_something);
std::thread t2(do_something);
t1.join();
t2.join();
return 0;
}
在这个例子中,do_something()
函数会执行一个循环,每执行 1000 次,就会调用 yield()
函数让出 CPU。这样可以避免某个线程一直占用 CPU,导致其他线程无法执行。
yield()
的使用场景:
- 避免线程饥饿: 当某个线程长时间占用 CPU 时,可以使用
yield()
让其他线程有机会执行。 - 提高程序的响应性: 当程序需要处理用户输入或者其他事件时,可以使用
yield()
让程序更及时地响应。
总结:
std::this_thread
模块虽然简单,但在多线程编程中却非常重要。它提供了获取线程 ID、睡眠和让出 CPU 的功能,可以帮助我们更好地管理线程,提高程序的性能和响应性。
函数名 | 功能 | 参数类型 | 返回值类型 |
---|---|---|---|
std::this_thread::get_id() |
获取当前线程的 ID。 | 无 | std::thread::id |
std::this_thread::sleep_for() |
使当前线程睡眠指定的时间长度。 | std::chrono::duration |
void |
std::this_thread::sleep_until() |
使当前线程睡眠到指定的时间点。 | std::chrono::time_point |
void |
std::this_thread::yield() |
使当前线程主动放弃 CPU 的使用权,让其他线程有机会执行。 | 无 | void |
希望今天的分享能帮助大家更好地理解和使用 std::this_thread
模块。记住,多线程编程是一门复杂的艺术,需要不断学习和实践。
结束语:多线程世界的掌舵者
掌握了 std::this_thread
,你就像一位经验丰富的船长,能够清晰地了解你的船员(线程)的身份,安排他们的休息时间,并确保他们能够协同工作,最终驶向成功的彼岸。 感谢各位的聆听,希望下次有机会再和大家分享更多的C++技术。