void thread1() {
std::string str;
while (true) {
{
std::lock_guard<std::mutex> lock(io_mtx);
std::cout << std::endl << "Enter line: ";
}
std::getline(std::cin, str);
std::unique_lock<std::mutex> lock(mtx);
if (str == "exit") break;
if (str.size() > 64 || !std::all_of(str.begin(), str.end(), ::isdigit)) {
{
std::lock_guard<std::mutex> iolock(io_mtx);
std::cerr << "String must include only numbers from 0 to 9 and less then 64 symbols\n";
}
continue;
}
// from my library(it sort and replace some exact symbols
sort_and_replace(str);
buffer.push(str);
cv.notify_one();
}
}
void thread2() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !buffer.empty(); });
std::string str = buffer.front();
buffer.pop();
{
std::lock_guard<std::mutex> iolock(io_mtx);
std::cout << "Processed line: " << str << std::endl;
}
size_t sum = sum_of_digits(str);
std::string sum_str = std::to_string(sum);
if (send(sockfd, sum_str.c_str(), sum_str.size(), 0) < 0) {
{
std::lock_guard<std::mutex> iolock(io_mtx);
std::cerr << "Error.\n";
}
close(sockfd);
connect_to_server();
}
}
}
int main() {
connect_to_server();
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
close(sockfd);
return 0;
}
This code does not display the output to the console correctly, namely the following output occurs:
Error connecting to program2, retrying...
Connected to server program2
Enter line: 123
Enter line: Processed line: 3KB1
123
Enter line: Processed line: 3KB1
1234
Enter line: Processed line: KB3KB1
123
Enter line: Processed line: 3KB1
1234
Enter line: Processed line: KB3KB1
How can i fix the code so that the output is:
Enter line: 1234
Processed line: KB3KB1
Enter line: 123
Processed line: 3KB1
I tried to use lock_guard but it doesn't work
you have a race, the first thread writes Enter line:
then the second thread writes Processed line: KB3KB1
then the user types 1234
.
In general, you cannot have two threads reading and writing to the terminal, otherwise this is a race, only one thread needs to read or write to the terminal, and you can use a TUI library if you want good control over the terminal.
In your case, you can have the first thread wait for the second thread to write Processed line: KB3KB1
before it writes Enter line:
, you can use a std::promise<void>
for this, it allows one thread to wait for a signal from another thread.
#include <thread>
#include <iostream>
#include <mutex>
#include <algorithm>
#include <queue>
#include <future>
#include <string>
std::mutex io_mtx;
std::mutex mtx;
std::condition_variable cv;
struct Message
{
std::promise<void> promise;
std::string message;
};
std::queue<Message> buffer;
void thread1() {
std::string str;
while (true) {
{
std::lock_guard<std::mutex> lock(io_mtx);
std::cout << "Enter line: ";
}
std::getline(std::cin, str);
std::promise<void> promise;
auto future = promise.get_future();
{
std::unique_lock<std::mutex> lock(mtx);
if (str == "exit") break;
if (str.size() > 64 || !std::all_of(str.begin(), str.end(), ::isdigit)) {
{
std::lock_guard<std::mutex> iolock(io_mtx);
std::cerr << "String must include only numbers from 0 to 9 and less then 64 symbols\n";
}
continue;
}
buffer.push({ std::move(promise),str});
}
cv.notify_one(); // signal other thread to read item
future.wait(); // waits for other thread to have read item
}
}
void thread2() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !buffer.empty(); });
auto message = std::move(buffer.front());
buffer.pop();
{
std::lock_guard<std::mutex> iolock(io_mtx);
std::cout << "Processed line: " << message.message << std::endl;
}
message.promise.set_value(); // signal other thread to continue
}
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
return 0;
}
Enter line: 12345
Processed line: 12345
Enter line: 12345
Processed line: 12345
Enter line:
that essentially serializes the code, but that's the only way to have 2 threads write to the terminal at the same time.
A more general solution is to use a TUI library and have the second thread send back a message to the first thread to asynchronously update the GUI with its output while waiting on user input, as this code is currently not extensible.
i'd also recommend that you drop using globals and instead collect those globals in a struct, so you can more easily reason about what each thread is using. globals are generally not thread-safe, and will introduce other maintainability problems down the road.
struct WorkerContext
{
std::mutex io_mtx;
std::mutex mtx;
std::condition_variable cv;
std::queue<Message> buffer;
};
// you can use a reference instead of shared_ptr
void thread1(std::shared_ptr<WorkerContext> context);
void thread2(std::shared_ptr<WorkerContext> context);