返回

C++ 中 std::thread 的基本用法

C++11 中引入了 std::thread 可以比较方便的创建和管理多线程,这篇笔记主要简单记录了一下我的学习过程。包括线程的创建的管理还有在类中相关的用法。

requirement

为了使用 std::thread 我们需要添加 <thread> 作为头文件,同时如果使用 cmake 进行项目编译管理的话,需要添加以下两行进行相关库的链接,然后就可以使用:

find_package (Threads)

...
add your executable
...

target_link_libraries (your_project_name ${CMAKE_THREAD_LIBS_INIT})

basic usage

std::thread 的使用比较简单,直接通过构造函数可以创建一个线程,有需要的话也可以传入参数,见以下代码:

// 无参数函数
void foo () {
    std::cout << "A thread function!" << std::endl;
}

// 有参数函数
void fooWithName (std::string func_name) {
    std::cout << "A thread function with name: " << func_name << "!" << std::endl;
}

int main() {

    std::thread t1(foo);
    t1.join();

    std::thread t2(fooWithName, "FuncName");
    t2.join();

    return 0;
}}

输出:

A thread function!
A thread function with name: FuncName!!

join() vs detach()

可以看到,上面代码例子中创建的两个线程后都增加了一条 join() 方法,这是因为通过 std::thread 创建的线程默认下并不是独立于主线程 (main())的,如果不加 join() 的话,当主线程退出之后子线程也会报错并退出,见如下代码:

void fooWithTime(int n_seconds, std::string name) {
    for (int i = 0; i < n_seconds; i++) {
        usleep(1000);
        std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
    }
}

int main() {

    std::thread t1(fooWithTime, 5, "Child");

    fooWithTime(3, "Main");

    t1.join();

    std::cout << "Main thread exit! " << std::endl;

    return 0;
}}

由于在 main 函数最后加入 t1.join() 所以在主函数输出 3 秒之后会等到 子线程工作完返回时才退出,输出如下:

Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 3 seconds..
Child Thread: worked for 3 seconds..
Child Thread: worked for 4 seconds..
Child Thread: worked for 5 seconds..
Main thread exit!

如果把 t1.join() 去掉之后,输出如下:

Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 3 seconds..
Main Thread: worked for 3 seconds..
Main thread exit!
terminate called without an active exceptionn

可以看到在主线程退出之后,子线程也被强制退出了,如果想让子线程独立于主线程的话,需要加入detach()

代码和输出如下:

void fooWithTime(int n_seconds, std::string name) {
    for (int i = 0; i < n_seconds; i++) {
        usleep(1000);
        std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
    }
}

int main() {

    std::thread t1(fooWithTime, 5, "Child");
    t1.detach();

    fooWithTime(3, "Main");


    std::cout << "Main thread exit! " << std::endl;

    return 0;
}
Main Thread: worked for 1 seconds..
Child Thread: worked for 1 seconds..
Main Thread: worked for 2 seconds..
Child Thread: worked for 2 seconds..
Main Thread: worked for 3 seconds..
Main thread exit!

可以发现,我们虽然 detach 了子线程,但是从输出上来看在主线程退出之后子线程也没有输出了,这是因为在子线程 detach 了之后,主线程退出的同时主进程也同时退出了,而我们运行进程时只能看到该进程的输出,所以就看不到 detach 后的线程的输出了。值得注意的是一旦线程被detach 之后就不能再进行 join 操作了,所以对 detach 的使用需要谨慎一点,并且在对一个线程进行 join 之前,应该通过 joinable() 进行判断。如下所示:

void fooWithTime(int n_seconds, std::string name) {
    for (int i = 0; i < n_seconds; i++) {
        usleep(1000);
        std::cout << name <<" Thread: worked for " << i + 1 << " seconds.." << std::endl;
    }
}

int main() {

    std::thread t1(fooWithTime, 5, "Child");
    t1.detach();

    if (t1.joinable()) {
        t1.join();
    } else {
        std::cout << "Thread unjoinable!" << std::endl;
    }

    fooWithTime(3, "Main");


    std::cout << "Main thread exit! " << std::endl;

    return 0;
}

threads with class

在实际使用中,我们的项目通过使用了各种类,下面代码演示了如何把类和 std::thread 结合使用:

class A {
public:
    std::string class_param;

    void funcWithoutParam() {
        std::cout << "non-static func: class param: " << class_param << std::endl;
    }

    void funcWithParam(std::string external_param) {
        std::cout << "non-static func: class param: " << class_param << ", external param: " << external_param << std::endl;
    }

    static void static_func(std::string param) {
        std::cout << "static func: param: " << param << std::endl;
    }
};

int main() {
    A a;
    a.class_param = "Apple";

    std::thread t1(&A::funcWithoutParam, a);
    t1.join();

    std::thread t2(&A::funcWithParam, a, "banana");
    t2.join();

    std::thread t3(&A::static_func, "Cherry");
    t3.join();

    std::cout << "Main thread exit! " << std::endl;

    return 0;
}

输出:

non-static func: class param: Apple
non-static func: class param: Apple, external param: banana
static func: param: Cherry
Main thread exit!

上面包含了三种用法:

  • 如果传入线程的函数是类中的 static 函数,可以直接传入函数指针和相关的参数,主要这里的函数比如显示地用 & 进行传入;
  • 如果传入线程的函数是 non-static 函数,我们还必须要传入该类的某个实例,并且这样传入的线程可以调用该实例所有变量,构造函数顺序是 类函数,实例,参数1, 参数2, ...

conclusion

这篇笔记简单概括了一下 std::thread,值得注意的是,上面的所有例子都直接最简单的应用,并没有涉及资源分配的情况,在实际使用需要注意不同线程的对同一资源的使用 (race_condition),避免出现死锁。

Built with Hugo
Theme Stack designed by Jimmy