Message Passing in C++

Pushpendra Sharma
CodeX
Published in
4 min readAug 5, 2024

Introduction

Message passing is a fundamental concept in concurrent and parallel programming. In C++, it enables effective communication between different threads or processes. Mastering message passing is essential whether you’re developing a high-performance application or orchestrating tasks in a multithreaded environment, as it can greatly improve your software’s efficiency and dependability.

Message Passing in C++
Message Passing in C++

What is Message Passing?

Message passing entails the transfer of messages (data) between various threads or processes. It serves as an alternative to shared memory concurrency, wherein multiple threads share access to a common memory area. Utilizing message passing can circumvent typical problems such as race conditions and deadlocks, rendering it a more secure option for concurrent programming.

Why Use Message Passing?

  1. Isolation: Threads or processes are isolated, reducing the risk of unintended interference.
  2. Scalability: Easier to scale across multiple processors or machines.
  3. Simplicity: Simplifies the design of concurrent programs by focusing on message exchange rather than shared memory management.
  4. Safety: Reduces the risk of concurrency bugs such as race conditions and deadlocks.

Implementing Message Passing in C++

C++ provides various tools and libraries to implement message passing. Two common approaches are using the Standard Template Library (STL) with std::thread and std::mutex, and using higher-level libraries like Boost or ZeroMQ.

Using std::thread and std::mutex

The STL provides basic tools for thread management and synchronization. Here’s a simple example using std::thread and std::mutex to simulate message passing.

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
std::queue<int> message_queue;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
message_queue.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one();
}
}
void consumer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !message_queue.empty(); });
int message = message_queue.front();
message_queue.pop();
std::cout << "Consumed: " << message << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}

In this example:

  • The producer function generates messages (integers) and pushes them into a queue.
  • The consumer function waits for messages and processes them.
  • std::mutex ensures that only one thread accesses the queue at a time.
  • std::condition_variable allows the consumer to wait for messages to be available.

Using Boost Libraries

Boost offers a comprehensive collection of libraries for C++ development, among which is Boost.Asio, known for facilitating network and low-level I/O programming tasks.

#include <boost/asio.hpp>
#include <iostream>
#include <thread>
using boost::asio::ip::tcp;void session(tcp::socket sock) {
try {
for (;;) {
char data[1024];
boost::system::error_code error;
size_t length = sock.read_some(boost::asio::buffer(data), error);
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
boost::asio::write(sock, boost::asio::buffer(data, length));
}
} catch (std::exception& e) {
std::cerr << "Exception in thread: " << e.what() << "\n";
}
}
int main() {
try {
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 12345)); for (;;) {
tcp::socket socket(io_context);
acceptor.accept(socket);
std::thread(session, std::move(socket)).detach();
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

In this example:

  • Boost.Asio is used to handle socket communication.
  • The session function reads data from the socket and writes it back, simulating message passing between a client and server.
  • The server listens for incoming connections and spawns a new thread for each connection.

Using ZeroMQ

ZeroMQ is a high-performance messaging library that provides an easy-to-use API for message passing.

#include <zmq.hpp>
#include <iostream>
#include <thread>
void server_thread() {
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REP);
socket.bind("tcp://*:5555");
while (true) {
zmq::message_t request;
socket.recv(request);
std::string msg(static_cast<char*>(request.data()), request.size());
std::cout << "Received: " << msg << std::endl;
zmq::message_t reply(5);
memcpy(reply.data(), "World", 5);
socket.send(reply, zmq::send_flags::none);
}
}
void client_thread() {
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REQ);
socket.connect("tcp://localhost:5555");
for (int i = 0; i < 10; ++i) {
zmq::message_t request(5);
memcpy(request.data(), "Hello", 5);
socket.send(request, zmq::send_flags::none);
zmq::message_t reply;
socket.recv(reply);
std::string msg(static_cast<char*>(reply.data()), reply.size());
std::cout << "Received: " << msg << std::endl;
}
}
int main() {
std::thread server(server_thread);
std::thread client(client_thread);
server.join();
client.join();
return 0;
}

In this example:

  • ZeroMQ is used to establish a client-server communication.
  • The server receives a message and sends a reply.
  • The client sends a message and waits for the reply.

Conclusion

Message passing in C++ is a potent method for concurrent programming in C++. Whether employing STL, Boost, or ZeroMQ, grasping message passing implementation can aid in creating efficient, dependable, and scalable applications. It simplifies concurrency by isolating threads and processes, thereby diminishing the complexity and hazards linked to shared memory, which is why numerous developers favor it.

--

--

Pushpendra Sharma
CodeX
Writer for

I am a Digital Marketing Executive, currently working in JavaTpoint Noida.