Thread creation using C++ std

The threads library was introduced in the C++11 standard, which was released in 2011. The motivation to add threads in C++ was to provide a standardized, portable, and efficient way to write concurrent programs in C++. Prior to the C++11 standard, the C++ language did not provide a standard way to create and manage threads, and developers had to rely on platform-specific APIs such as POSIX threads or Windows threads. This made it difficult to write portable and maintainable concurrent code. With the addition of the thread library to the C++ standard, developers can now write portable and standardized multithreaded code that can run on any platform that supports the C++ standard library.

Additionally, the rise of multicore processors made multithreaded programming an important technique for improving the performance of software applications. The addition of the thread library to C++ provides developers with a powerful tool for writing efficient and scalable multithreaded code that can take advantage of the increased processing power of modern CPUs.

How to create thread in C++ std?

There are several ways to create threads in C++ using the standard library.

Let me list down few of them here:

1.Using the function std::thread constructor: This is the most common way to create threads in C++. You can pass a function or a lambda expression to the constructor, and a new thread will be created that will execute that function or lambda expression.

#include<iostream>
#include <thread>

void my_thread_function(int* pData)
{
    std::cout << "Hello from my_thread_function! 
        and data passd ="<<*pData << std::endl;
}

int main()
{
    int data = 101;
    std::thread my_thread(my_thread_function,&data);
    my_thread.join();
    return 0;
}
// O/P - Hello from my_thread_function! and data passd =101        

2. Using Functor: In C++, a functor is an object that can be treated like a function or a function pointer. Functors are also known as function objects, as they encapsulate a function or a code block within an object, which can be called like a regular function.

Functors are often used in C++ to provide a more flexible and object-oriented way of calling functions. They can be used to provide additional state or context to a function, which can be helpful in some cases, such as when working with algorithms that require a comparison function or when implementing callbacks and of course in this case thread creation.

To create a functor in C++, you define a class with an overloaded operator() function. This allows instances of the class to be called like a regular function.

#include <iostream>
#include <thread>

class MyThread {
public:
? ? void operator()() {
? ? ? ? std::cout << "Hello from thread!\n";
? ? }
};

int main() {
? ? std::thread t{MyThread()};
? ? t.join();
? ? return 0;
}
// O/P => Hello from thread!        

3. Using a lambda function: Allows you to define an anonymous function on the fly. It is a shorthand way of defining a function object (a functor) without having to define a separate class or function for it. Lambda functions can be used wherever a function object is required, such as in STL algorithms, for callbacks, threads or for creating closures. Lambda functions can capture variables from their enclosing scope by value or by reference and can have parameters and a return value. The syntax for defining a lambda function uses the lambda operator [], followed by a parameter list (if any), and then the function body as below:

[capture clause] (parameter list) -> return type { function body }

  1. The capture clause is optional,
  2. The parameter list may be empty,
  3. The return type may be omitted if the function returns void.
  4. The function body may contain any valid C++ statements, including other lambda expressions.

// Thread using lamda.
#include <iostream>
#include <thread>

int main() {
? ? std::thread t([](){
? ? ? ? std::cout << "Hello from lamda thread!\n";
? ? });
? ? t.join();
? ? return 0;
}

//Op => Hello from lamda thread!        

4. Using a member function and an object: In the below example of creating a thread using a member function and an object, &my_object is passed as the second parameter to std::thread() because we need to provide the instance of the class on which the member function should be called.

When we pass a member function to the std::thread() constructor, we also need to provide the object on which that function should be called. The second parameter to the std::thread() constructor is a pointer to the object on which the member function should be called.

#include <iostream>
#include <thread>

class MyClass {
public:
? ? void my_member_function() {
? ? ? ? std::cout << "Hello from member function thread!\n";
? ? }
};

int main() {
? ? MyClass my_object;
? ? std::thread t(&MyClass::my_member_function, &my_object);
? ? //When the thread starts running, it will call my_member_function() on my_object.
? ? t.join();
? ? return 0;
}
// Op => Hello from member function thread!        

5. Using a packaged task:A packaged_task is a class template in C++ that encapsulates a task or a callable object that can be invoked asynchronously. It provides a convenient way to package a function or callable object along with its associated arguments and return value.

The packaged task is a way to pass a function, which returns a value or takes an argument, to a thread for execution. It is useful when we want to execute a function in a thread and get its return value.

packaged_task can be used in scenarios where we want to perform some computation asynchronously and retrieve the result later. It can be used in combination with other concurrency-related classes such as thread, async, and future.

#include <iostream>
#include <future>
#include <thread>
using namespace std;

int add2Nums(int x, int y) {
? ? return x + y;
}
int main() {
? ? packaged_task<void()> task([](){
? ? ? ? cout << "Hello from packaged_taskthread 1!\n";
? ? });
? ? future<void> future = task.get_future();
? ? thread t(std::move(task));
? ? future.wait();
? ? if(t.joinable()) {
? ? ? ? t.join();
? ? }
? ??
	// Second pkgd thread created with seperate function .
? ? packaged_task<int(int, int)> task1(add2Nums); // create packaged_task with my_function
? ? future<int> result = task1.get_future(); // get future from packaged_task


? ? thread t1(std::move(task1), 10, 20); // pass packaged_task to thread


? ? cout << "Waiting for result from secind thread ...\n";
? ? int res = result.get(); // wait for result from packaged_task
? ? cout << "Result from send packed thread : " << res << std::endl;
? ? if(t1.joinable()) {
? ? ? ? t1.join();
? ? }
? ? return 0;
}
// Op => 
//amit@DESKTOP-9LTOFUP:~/OmPracticeC++/Threads$ ./a.out
//Hello from packaged_taskthread 1!
//Waiting for result from secind thread ...
//Result from send packed thread : 30        

6. Using an async function: Using the function async: This function creates a new thread and returns a future object that can be used to retrieve the result of the thread's execution. For example:

#include <iostream>
#include <future>
using namespace std;

int my_thread_function() {
? ? return 101;
}

int main() {
? ? future<int> result = async(launch::async, my_thread_function);
? ? cout << "From thread created using async Result: " << result.get() << std::endl;
? ? return 0;
}
// Op => From thread created using async Result: 101        

7. Using the function thread constructor: This is a new feature introduced in C++20. It is similar to std::thread, but provides additional safety guarantees by automatically joining or detaching the thread when the std::jthread object is destroyed. This is not yet supported by the gcc compiler 12.2.0(The latest one when I was writing this article ). I could not test this , hence I am not writing the example code here.


When to use what ?

  1. thread: This is the most flexible option for creating threads, as it allows you to specify any function or function object as the thread's entry point. This is a good choice for most situations where you need to create a new thread.
  2. async: This is a good option when you need to run a task asynchronously and don't need to explicitly manage the thread. It's also useful when you want to return a future object from a function.
  3. packaged_task: This is a good option when you need to run a task asynchronously and want to return a future object from a function, but want more control over the creation and management of the thread.

Above example use future object and async, below are details.

1.future : is a C++11 standard library class template that represents a value or an exception that may not be ready yet but will be available in the future. It provides a way to asynchronously retrieve the result of a function call.

Here's an example that shows how to use future

#include <iostream>
#include <future>
#include <thread> 
// for using the ?std::this_thread::sleep_for
#include <chrono> //provides a set of classes to perform time-related operations.
using namespace std;
int add(int a, int b) {
    this_thread::sleep_for(std::chrono::seconds(2));
    return a + b;
}

int main() {
    future<int> result = async(launch::async, add, 3, 4);
    cout << "Waiting for result..." << std::endl;
    int sum = result.get();
    cout << "Result: " << sum << std::endl;
    return 0;
}
// Op => 
// Waiting for result..
// Result: 7.        

In the above example, async is used to asynchronously execute the add function with arguments 3 and 4. It returns a future<int> object that will eventually hold the result of the computation. The launch::async policy is used to ensure that the function runs asynchronously in a new thread.

The main thread then waits for the result using the get member function of the future object. The get function blocks until the result is available, at which point it returns the result.

Note that the add function deliberately sleeps for 2 seconds to simulate a long-running computation that might benefit from asynchronous execution.


2. async : Is a C++11 standard library function that creates and runs a function asynchronously, potentially on a different thread. It returns an object of type future, which can be used to retrieve the return value of the function.

The syntax for async is below :

future<T> async(launch policy, Callable&& f, Args&&... args);

where policy is an optional argument that specifies how the function should be executed, f is the function to execute, and args are the arguments to pass to the function.

The policy argument can take on one of three values:

  • launch::async: the function is executed asynchronously in a separate thread, and the async function returns immediately.
  • launch::deferred: the function is executed synchronously when the future object's get() method is called.
  • launch::async | launch::deferred: the implementation chooses whether to execute the function asynchronously or synchronously. Its the default behavior With this launch policy it can run asynchronously or not depending on the load on system. But we have no control over it.

Note: If we do not specify an launch policy. Its behavior will be similar to launch::async | launch::deferred.

Here's an example of how to use async to execute a function asynchronously:

#include <iostream>
#include <future>

int add(int x, int y) {
    return x + y;
}

int main() {
    // Create a future object by asynchronously executing the add function
    std::future<int> fut = std::async(std::launch::async, add, 2, 3);

    // Do other work while the function executes asynchronously
    std::cout << "Doing other work..." << std::endl;

    // Retrieve the result of the function
    int result = fut.get();

    // Print the result
    std::cout << "The result is: " << result << std::endl;

    return 0;
}
// Op => Doing other work..
// The result is: 5.        

In this example, we create a future<int> object by calling async with add as the function to execute and 2 and 3 as the arguments to pass to the function. The async function returns immediately, and the function add is executed asynchronously in a separate thread.

While the function is executing asynchronously, the program can do other work. In this example, we print a message to the console. When the result of the function is needed, we call the get method on the future object to retrieve the result. Finally, we print the result to the console.

If you know any other way to create the thread using C++ std lib, please let me know in the comments.

Thank you for reading till end. I am planning to write the thread creation using POSIX in next article.

要查看或添加评论,请登录

Amit Nadiger的更多文章

  • Atomics in Rust

    Atomics in Rust

    Atomics in Rust are fundamental building blocks for achieving safe concurrent programming. They enable multiple threads…

  • Frequently used Thread API - Random notes

    Frequently used Thread API - Random notes

    Thread Creation and Management: thread::spawn: Creates a new thread and executes a closure within it. It returns a…

  • Difference b/w Cell and RefCell

    Difference b/w Cell and RefCell

    Both Cell and RefCell are used in Rust to introduce interior mutability within immutable data structures, which means…

  • Tokio::spawn() in depth

    Tokio::spawn() in depth

    Tokio::spawn() is a function provided by the Tokio runtime that allows you to create a new concurrent task. Unlike…

  • tokio::spawn() Vs Async block Vs Async func

    tokio::spawn() Vs Async block Vs Async func

    Asynchronous programming is a powerful paradigm for handling I/O-bound operations efficiently. Rust provides several…

  • Tokio Async APIS - Random notes

    Tokio Async APIS - Random notes

    In this article, we will explore how to effectively use Tokio and the Futures crate for asynchronous programming in…

  • Reactor and Executors in Async programming

    Reactor and Executors in Async programming

    In asynchronous (async) programming, Reactor and Executor are two crucial components responsible for managing the…

  • Safe Integer Arithmetic in Rust

    Safe Integer Arithmetic in Rust

    Rust, as a systems programming language, emphasizes safety and performance. One critical aspect of system programming…

  • iter() vs into_iter()

    iter() vs into_iter()

    In Rust, iter() and into_iter() are methods used to create iterators over collections, but they have distinct…

  • Zero-cost abstraction in Rust

    Zero-cost abstraction in Rust

    Rust supports zero-cost abstractions by ensuring that high-level abstractions provided by the language and standard…

社区洞察

其他会员也浏览了