Master C++ Concurrency: Processes, Threads, and IO Multiplexing Explained
This article explores C++ concurrent programming techniques, covering multi‑process fundamentals, process creation with fork(), inter‑process communication methods, multi‑threading using std::thread, synchronization tools like mutexes and condition variables, and efficient I/O handling through select, poll, and epoll multiplexing, with practical code examples.
Part1: Multi‑process – Independent “small worlds”
Multi‑process means a program runs several independent tasks, each in its own process with separate memory and resources. In Linux, processes are created with fork(), which duplicates the calling process; the child receives a return value of 0, while the parent receives the child’s PID. Example code demonstrates creating a child and printing its PID.
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
printf("I am the child process, my PID is %d, my parent's PID is %d
", getpid(), getppid());
} else {
printf("I am the parent process, my PID is %d, my child's PID is %d
", getpid(), pid);
}
return 0;
}Inter‑process communication (IPC) allows processes to exchange data. Common IPC mechanisms include pipes, message queues, and shared memory. The following snippet shows parent‑child communication via a pipe.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main() {
int pipe_fd[2];
pid_t pid;
char buffer[BUFFER_SIZE];
if (pipe(pipe_fd) == -1) {
perror("pipe creation failed");
return 1;
}
pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
close(pipe_fd[0]);
const char *message = "Hello from child";
write(pipe_fd[1], message, strlen(message));
close(pipe_fd[1]);
} else {
close(pipe_fd[1]);
ssize_t bytes_read = read(pipe_fd[0], buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received from child: %s
", buffer);
}
close(pipe_fd[0]);
}
return 0;
}Advantages of multi‑process
Process isolation: a crash in one process does not affect others, improving stability.
Clear resource allocation: each process has its own resources, simplifying management.
Disadvantages of multi‑process
Complex IPC: communication requires extra mechanisms and careful synchronization.
Higher overhead: creating and destroying processes consumes more CPU and memory.
Part2: Multi‑threading – Lightweight collaboration
Threads share the same process resources, allowing concurrent execution within a single address space. C++11 introduced std::thread for portable thread creation. Simple examples show creating a thread to print a message and using a lambda to capture external variables.
#include <iostream>
#include <thread>
void print_message() {
std::cout << "This is a message from the thread." << std::endl;
}
int main() {
std::thread t(print_message);
t.join();
return 0;
} #include <iostream>
#include <thread>
int main() {
int value = 42;
std::thread t([&]() {
std::cout << "The value is: " << value << std::endl;
});
t.join();
return 0;
}Thread synchronization
To avoid data races, C++ provides std::mutex and std::condition_variable. The following example protects a shared counter with a mutex.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
mtx.lock();
++shared_data;
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "The final value of shared_data is: " << shared_data << std::endl;
return 0;
}A producer‑consumer model using a condition variable demonstrates coordination between threads.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
void producer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
data_queue.push(i);
lock.unlock();
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
lock.unlock();
std::cout << "Consumed: " << data << std::endl;
if (data == 9) break;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}Advantages of multi‑threading
Easy resource sharing and communication.
Lower context‑switch overhead compared to processes.
Improved responsiveness in GUI applications.
Disadvantages of multi‑threading
Potential for race conditions and deadlocks if synchronization is misused.
Increased programming complexity and debugging difficulty.
Performance can degrade with excessive threads due to scheduling overhead.
Part3: IO Multiplexing – Efficient I/O management
IO multiplexing lets a single thread monitor many file descriptors. Linux provides select, poll, and epoll. select works on all platforms but limits the number of descriptors and copies descriptor sets on each call.
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);Example: a simple TCP server using select to handle new connections and incoming data.
#include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
#define MAX_CLIENTS 10
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket creation failed");
return 1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_socket);
return 1;
}
if (listen(server_socket, 3) == -1) {
perror("listen failed");
close(server_socket);
return 1;
}
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_socket, &read_fds);
int max_fd = server_socket;
while (true) {
fd_set temp_fds = read_fds;
int activity = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
if (activity == -1) {
perror("select error");
break;
} else if (activity > 0) {
if (FD_ISSET(server_socket, &temp_fds)) {
int client_socket = accept(server_socket, NULL, NULL);
if (client_socket != -1) {
FD_SET(client_socket, &read_fds);
if (client_socket > max_fd) max_fd = client_socket;
}
}
for (int i = 0; i <= max_fd; ++i) {
if (FD_ISSET(i, &temp_fds) && i != server_socket) {
char buffer[1024] = {0};
int valread = read(i, buffer, sizeof(buffer));
if (valread <= 0) {
close(i);
FD_CLR(i, &read_fds);
} else {
std::cout << "Received: " << buffer << std::endl;
}
}
}
}
}
close(server_socket);
return 0;
} pollremoves the descriptor limit and separates parameters, but still copies large arrays between user and kernel space.
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout); epoll(Linux‑specific) offers the best performance for large numbers of descriptors, using an event‑driven model.
#include <sys/epoll.h>
int epoll_create(int size); // size is ignored after Linux 2.6.8
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);Choosing the right mechanism depends on portability, descriptor count, and performance requirements.
Part4: Comparing the three approaches
Multi‑process provides strong isolation suitable for critical server components; multi‑threading offers lightweight sharing for tightly coupled tasks; IO multiplexing excels at handling massive concurrent I/O with minimal threads. In real C++ projects, developers must weigh these trade‑offs to select the most appropriate concurrency model for efficiency and stability.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
