C++ `std::this_thread` 模块:获取当前线程 ID 与睡眠操作

好的,没问题。

各位观众,各位朋友,各位优秀的程序员们,欢迎来到今天的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;
}

这段代码创建了两个线程 t1t2,每个线程都执行 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++技术。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注