C++11 introduces std::thread to make it easier to create and manage multiple threads, and this note briefly documents my learning process. This note is a brief record of my learning process, including the management of thread creation and related usage in classes.

requirement

In order to use std::thread we need to add <thread> as a header file, and if we use cmake for project compilation management, we need to add the following two lines to link the relevant libraries, and then we can use them.

1
2
3
4
5
6
7
find_package (Threads)

...
add your executable
...

target_link_libraries (your_project_name ${CMAKE_THREAD_LIBS_INIT})

basic usage

The use of std::thread is relatively simple, a thread can be created directly through the constructor, and parameters can be passed in if needed, see the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 无参数函数
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;
}}

Output.

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

join() vs detach()

This is because the thread created by std::thread is not independent of the main thread (main()) by default. If join() is not added, the child thread will also exit with an error when the main thread exits, see the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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;
}}

Since t1.join() is added at the end of the main function, it will wait until the subthread returns from its work after 3 seconds of output from the main function, and the output will be as follows.

1
2
3
4
5
6
7
8
9
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!

If t1.join() is removed, the output is as follows.

1
2
3
4
5
6
7
8
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

You can see that after the main thread exits, the child thread is also forced to exit. If you want the child thread to be independent of the main thread, you need to add detach()

The code and output are as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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;
}
1
2
3
4
5
6
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!

We can see that although we detach the sub-thread, the output of the sub-thread is not output after the main thread exits. This is because after the sub-thread is detached, the main process also exits at the same time as the main thread exits, and we can only see the output of the process when we run the process, so we cannot see the output of the thread after detach. It is worth noting that once a thread has been detached, it can no longer be join, so you need to be careful with detach, and you should use joinable() to determine before you join a thread. This is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

In practice, our project uses various classes, and the following code demonstrates how to use classes in conjunction with std::thread.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}

Output:

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

The above contains three uses.

  • If the function passed into the thread is a static function in the class, you can pass in the function pointer and associated arguments directly, mainly here the function is passed in with & as shown for example.
  • If the function passed into the thread is a non-static function, we must also pass in some instance of the class, and the thread so passed in can call all variables of that instance, with the constructor order being class function, instance, parameter 1, parameter 2, ...

conclusion

This note gives a brief overview of std::thread, and it is worth noting that all the examples above are straightforward and do not involve resource allocation, so in practice you need to pay attention to the use of the same resource (race_condition) by different threads to avoid deadlocks.