| Home | Libraries | People | FAQ | More

## Using the mqtt_client in a Multithreaded Environment

This chapter provides information about thread safety of the ``mqtt_client`` and other Boost.Asio-compliant objects and provides examples of how to write thread-safe code in multithreaded environments.

### Thread Safety of ASIO-Compliant Objects

A common misconception exists regarding the "thread safety" of ASIO-compliant asynchronous objects, specifically around the belief that initialising such an object with a `boost::asio::io_context::strand` [7] executor allows its asynchronous functions (`async_xxx`) to be freely called from any executor or thread. That is not correct. Those `async_xxx` functions themselves must be called from within the same executor.

Every `async_xxx` function in every ASIO-compliant object begins by executing some initiation code before typically proceeding to call an intermediate lower-level ASIO `async_xxx` function, with an adapted handler serving as the callback. It is worth noting that the thread safety of this initiation code, which is called directly by the `boost::asio::async_initiate` function and executed by the same executor that called the `async_xxx` function, is not guaranteed and depends on the implementation itself. This uncertainty around thread safety is what the notation "Thread Safety: Shared objects: Unsafe" means, which appears in the documentation for any ASIO-compliant object.

Consequently, similar to the other ASIO-compliant objects, the ``mqtt_client`` object is not thread-safe. Invoking its member functions concurrently from separate threads will result in a race condition.

This design choice is intentional and offloads the responsibility of managing concurrency to the user. Given that many applications using Boost.Asio often operate in a single-threaded environment for better performance, ASIO-compliant objects do not want to pay the cost of the overhead associated with mutexes and other synchronization mechanisms. Instead, it encourages developers to implement their own concurrency management strategies, tailoring them to the specific needs of their applications.

### Executors, Threads, and Strands

Before delving into thread-safe programming, it is essential to understand the distinction between executors and threads. Executors are not threads but mechanisms for scheduling how and when work gets done. An executor can distribute tasks across multiple threads, and a single thread can execute tasks from multiple executors. Thus, when several threads invoke `boost::asio::io_context::run()` on the same `boost::asio::io_context`, the underlying executor of that `io_context` has the flexibility to assign tasks to any of those threads.

A `boost::asio::io_context::strand` executor is particularly important in maintaining thread safety and managing multithreading. As outlined earlier, this type of executor guarantees that tasks assigned to it are executed in a serialised manner, preventing concurrent execution. It is important to note that this serialisation does not mean that a single thread handles all tasks within a strand. If the `io_context` associated with a strand operates across multiple threads, these threads can independently undertake tasks within the strand. However, these tasks are executed in a non-concurrent fashion as guaranteed by the strand. Refer to Strands: Use Threads Without Explicit Locking for more details.

### Writing Thread-Safe Code

As mentioned previously, it is the user's responsibility to ensure that none of the ``mqtt_client``'s member functions are called concurrently from separate threads. To achieve thread safety in a multithreaded environment, all the ``mqtt_client``'s member functions must be executed within the same implicit [8] or explicit strand.

Specifically, use `boost::asio::post` or `boost::asio::dispatch` to delegate a function call to a strand, or `boost::asio::co_spawn` to spawn the coroutine into the strand. For asynchronous functions, this will ensure that the initiation code is executed within the strand in a thread-safe manner. ``mqtt_client``'s executor must be that same strand. This will guarantee that the entire sequence of operations - the initiation code and any intermediate operation - is carried out within the strand, thereby ensuring thread safety.

| Important
To conclude, to achieve thread safety, all the member functions of the ``mqtt_client`` must be executed in the same strand. This strand must be given in the ``mqtt_client`` constructor.

The examples below demonstrate how to publish a "Hello World" Application Message in a multithreaded setting using callbacks (`post`/`dispatch`) and coroutines (`co_spawn`):

- hello_world_in_multithreaded_env.cpp
- hello_world_in_coro_multithreaded_env.cpp

[7] An executor that provides serialised handler execution.

[8] Only one thread is calling `boost::asio::io_context::run()`.