C++ Multithreaded Quick Start: Basic & Common Operations

case1: create thread 1 join, detach

Create a thread and wait for it to finish executing and print the id of both threads

#include <iostream>
#include <thread>
using namespace std;
void func() {
    cout << "hello , this is my thread, thread id is " << this_thread::get_id() << endl;
}
int main() {
    thread th = thread(func);
    th.join();
    cout << "this is main thread and its id is " << this_thread::get_id() << endl;
}

The results are as follows:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
hello , this is my thread, thread id is 2
this is main thread and its id is 1

Process finished with exit code 0

Using detach, discard control of the thread:

#include <iostream>
#include <thread>

using namespace std;

void func() {
    cout << "hello , this is my thread, thread id is " << this_thread::get_id() << endl;
}
int main() {
    thread th = thread(func);
    th.detach();
    // detach can be used if we are no longer concerned with the running of this thread at this time
    cout << th.joinable() << endl;
    cout << "this is main thread and its id is " << this_thread::get_id() << endl;
}

Run result:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
0
this is main thread and its id is 1
hello , this is my thread, thread id is 2

Process finished with exit code 0

case2: Create thread 2 threads to pass values or references

Pass Value

#include <iostream>
#include <thread>

using namespace std;

void func(string s) {
    cout << "hello , this is my thread, thread arg is " << s << endl;
}
int main() {
    thread th = thread(func, "test");
    th.join();
    cout << "this is main thread and its id is " << this_thread::get_id() << endl;
}

The printout is as follows:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
hello , this is my thread, thread arg is test
this is main thread and its id is 1

Process finished with exit code 0

Pass-by reference

#include <iostream>
#include <thread>

using namespace std;

void func(string& s) {
    cout << (&s) << endl;
    cout << "hello , this is my thread, thread arg is " << s << endl;
}
int main() {
    string str = "test";
    thread th = thread(func, ref(str));
    cout << (&str) << endl;
    th.join();
    cout << "this is main thread and its id is " << this_thread::get_id() << endl;
}

The printout is as follows:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
0x62fd10
0x62fd10
hello , this is my thread, thread arg is test
this is main thread and its id is 1

Process finished with exit code 0

case3: Create a thread to pass a functional object as a parameter

As to the definition of the analog function, you can see: functor
If you simply pass a logical function into a thread, it is not good when the logic of the function is more complex.
Encapsulating a function inside a class gives it some encapsulation and creates a data structure inside the class, such as a map, to record the state of the function.
Function-like, parameterized

#include <iostream>
#include <thread>

using namespace std;

// functional object
struct A {
    void operator()() {
        cout << "I'm A" << endl;
    }
};
void show() {
    cout << "I'm show" << endl;
}
int main() {
    show();
    A a;
    a();    // Equivalent to a.operator();
    // We call this object callable, also known as a function-like
    thread thread1 = thread(A());
    thread1.join();
    return 0;
}

The printout is as follows:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
I'm show
I'm A
I'm A

Process finished with exit code 0

Parameterized function

#include <iostream>
#include <thread>

using namespace std;

// functional object
struct A {
    void operator()(int num) {
        for (int i = 0; i < num; i++) {
            cout << "I'm A" << i << endl;
        }
    }
};

int main() {
    int num = 10;
    thread thread1 = thread(A(), num);
    for (int i = 0; i < num; i++) {
        cout << "I'm main" << i << endl;
    }
    thread1.join();
    return 0;
}

The printout is out of order and multithreaded as follows
And cout s aren't thread safe, so you can see that they look messy

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
I'm mainI'm A00

I'm A1
I'm A2
I'm A3
I'm AI'm main41

I'm mainI'm A25

I'm mainI'm A36

I'm AI'm main47

I'm main5
I'm mainI'm A86

I'm mainI'm A97

I'm main8
I'm main9

Process finished with exit code 0

lambda function as parameter

#include <iostream>
#include <thread>

using namespace std;

int main() {
    string s = "test";
    thread f = thread([&s](int a,int b) {
        cout << s << endl;
        cout << a + b << endl;
    }, 2, 3);
    f.join();
    return 0;
}

Print results:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
test
5

Process finished with exit code 0

case4: Watch for multithreaded programs to speed up calculations

#include <iostream>
#include <thread>

using namespace std;

// Measure the running time of a function
template <class T>
void measure(T&& func) {
    using namespace std::chrono;
    auto start = system_clock::now();
    // func
    func();
    duration<double> diff = system_clock::now() - start;
    cout << "Executed" << diff.count() << "second" << endl;
}

// Sum function [start,end)
void sum(long start, long end, long& ans) {
    long s = 0;
    for(auto i = start; i < end; i++) {
        s += i;
    }
    ans = s;
}

const long S = 100000000;

int main() {
    // Measure how long the work is allocated to two threads
    measure([](){
        long ans1, ans2;
        thread t1 = thread(sum, 0, S >> 1, std::ref(ans1));
        thread t2 = thread(sum, S >> 1, S, std::ref(ans2));
        t1.join();
        t2.join();
        cout << "ans = " << ans1 + ans2 << endl;
    });
    // Measure single-threaded working hours
    measure([](){
        long ans;
        sum(0, S, ans);
        cout << "ans = " << ans << endl;
    });
    return 0;
}

Print results:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
ans = 887459712
 Executed 0.13546 second
ans = 887459712
 Executed 0.240006 second

Process finished with exit code 0

Of course, here's one detail:
If we pass in a ref reference in a multi-threaded program, it is better not to operate directly on this ref value in a threaded program, but to use a temporary variable, such as s in the program above, which is added up and then assigned to ans. This will improve the efficiency of the program.

case5:future + get to get concurrent results

The future package thread runs as follows:
Since the result of our thread function is long, define future <long>

#include <iostream>
#include <thread>
#include <future>
#include <vector>

using namespace std;

// Measure the running time of a function
template <class T>
void measure(T&& func) {
    using namespace std::chrono;
    auto start = system_clock::now();
    // func
    func();
    duration<double> diff = system_clock::now() - start;
    cout << "Executed" << diff.count() << "second" << endl;
}

// Sum function [start,end)
long sum(long start, long end) {
    long s = 0;
    for(auto i = start; i < end; i++) {
        s += i;
    }
    return s;
}

const long S = 100000000;

int main() {
    // Measure how long it takes to allocate work to threadNums threads
    measure([](){
       const long threadNums = 8;
       vector<future<long>> vec;
       vec.reserve(threadNums);
       for (int i = 0; i < threadNums; i++) {
           vec.push_back(async(sum, (S / threadNums) * i, (S / threadNums) * (i + 1)));
       }
       long ans = 0;
       // get blocked concurrent results
       for (int i = 0; i < threadNums; i++) {
           ans += vec[i].get();
       }
       cout << "ans = " << ans << endl;
    });
    // Measure single-threaded working hours
    measure([](){
        long ans = sum(0, S);
        cout << "ans = " << ans << endl;
    });
    return 0;
}

The results are as follows:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
ans = 887459712
 Executed 0.0521455 second
ans = 887459712
 Executed 0.250044 second

Process finished with exit code 0

case6: mutex

Multiple threads access the same value and modify it, resulting in data competition.

#include <iostream>
#include <thread>
#include <future>
#include <vector>

using namespace std;
// Measure the running time of a function
template <class T>
void measure(T&& func) {
    using namespace std::chrono;
    auto start = system_clock::now();
    // func
    func();
    duration<double> diff = system_clock::now() - start;
    cout << "Executed" << diff.count() << "second" << endl;
}

std::mutex mtx;
// Thread-safe summation function
void sum(long& s) {
    mtx.lock();
    for (int i = 0; i < 100000; i++) {
        s++;
    }
    mtx.unlock();
}

int main() {
    // Measure how long it takes to allocate work to threadNums threads
    measure([](){
        vector<thread> v;
        long s = 0;
        for (int i = 0; i < 4; i++) {
             v.emplace_back(std::thread(sum, std::ref(s)));
         }
        for (int i = 0; i < 4; i++) {
            v[i].join();
        }
        cout << "ans " << s << endl;
    });
    measure([](){
        long s = 0;
        for (int i = 0; i < 4; i++) {
            sum(s);
        }
        cout << "ans " << s << endl;
    });
    return 0;
}

Test results:

C:\Users\LENOVO\CLionProjects\untitled\cmake-build-debug\untitled.exe
ans 400000
 Executed 0.0141654 second
ans 400000
 Executed 0.0155926 second

Process finished with exit code 0

Note reference:
C++ Multithreaded Quick Start

Tags: C++ Back-end Multithreading

Posted on Thu, 21 Oct 2021 12:24:48 -0400 by giovo