Why does the following program printf "thread 1 exists" twice in WSL2?

157 views Asked by At

I am currently working with C++ multithreading on WSL2 and I've encountered something stumping. When running my program, I'm receiving duplicate printf output from a single thread. Below is the program that I'm running:

#include <atomic>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <iostream>
#include <future>

std::atomic<int> g_Flag = 0;
std::atomic<int> cnt = 0;

void worker1(std::future<int> fut) {
    printf("this is thread 1\n");
    g_Flag = 1;
    cnt++;
    fut.get();
    printf("thread 1 exists\n");
}

void worker2(std::promise<int> prom) {
    printf("this is thread 2\n");
    g_Flag = 2;
    cnt++;
    prom.set_value(10);
    printf("thread 2 exists\n");
}

 
using namespace std::chrono_literals;
int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t1(worker1, std::move(fut));
    std::thread t2(worker2, std::move(prom));
    while (cnt.load() != 2) {

    }
    t1.detach();
    t2.detach();
    printf("main exists\n");
    return 0;
}

The program output:

eugene@DESKTOP-P8P395D:~/tron$ ./a.out 
this is thread 1
this is thread 2
main exists
thread 2 exists
thread 1 exists
thread 1 exists

As you can see, it prints "thread 1 exists" twice. Any insights on why this could be happening? I believe it may have to do with the WSL2 environment, as under normal circumstances, I would only expect each thread to print its exit message once. Any help or direction is appreciated. Thanks!

1

There are 1 answers

2
Amit On

Your output:

this is thread 1
this is thread 2
main exists
thread 2 exists
thread 1 exists
thread 1 exists

Before I see that "it prints "thread 1 exists" twice.", I see that it prints after "main exists":

  • It's not recommended to use standard output (stdout) from a detached thread after the main() function exits.

  • It’s generally a good practice to ensure all threads have completed before allowing the main() function to exit.

(Mitigation) To ensure main() exits only after workers has nothing to do / access nothing anymore, cnt is incremented at the very end of worker function:


void worker1(std::future<int> fut) {
    // (The original code, minus cnt++)
    cnt++;
}

void worker2(std::promise<int> prom) {
    // (The original code, minus cnt++)
    cnt++;
}

--

The recommended approach involves restructuring the code:

#include <thread>
#include <iostream>
#include <future>
#include <syncstream>


void log(const char* str)
{
     std::osyncstream ss(std::cout);
     ss << str << std::endl;
}


void worker1(std::future<int> fut) 
{
    log("this is thread 1");
    fut.get();
    log("thread 1 exists");
}


void worker2(std::promise<int> prom) 
{
    log("this is thread 2");
    prom.set_value(10);
    log("thread 2 exits");
}


int main()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    // Fire the 2 threads:
    std::thread t1(worker1, std::move(fut));
    std::thread t2(worker2, std::move(prom));

    t1.join();
    t2.join();

    log("main exits");
}

Key points:

  • IMPORTANT: Replace the while loop ("busy wait") and detach() with join() in the main() to ensure that the main thread waits for all child threads to finish before exiting.
  • Dilute the #include lines to include only what's necessary - For better practice.
  • Remove unused variables - For better practice.
  • Remove the unused using namespace directive - For better practice.
  • In addition, I would also replace the printf() calls with std::osyncstream.

Demo

Now, the output is:

this is thread 1
this is thread 2
thread 2 exits
thread 1 exits
main exits

--

UPDATE:

Due to your comment: "The detach used but not join here is the requirements from test. I cannot change that.". This solution works with detach():

#include <thread>
#include <iostream>
#include <future>
#include <syncstream>
#include <mutex>
#include <condition_variable>


std::mutex mtx{};
std::condition_variable cv{};
uint8_t workers_finished{ 0 }; // Counter for finished workers


void log(const char* str)
{
     std::osyncstream ss(std::cout);
     ss << str << std::endl;
}


void worker1(std::future<int> fut) 
{
    log("this is thread 1");
    fut.get();
    log("thread 1 exits");

    std::lock_guard lock(mtx);
    ++workers_finished;
    cv.notify_one(); // Signal main thread
}


void worker2(std::promise<int> prom) 
{
    log("this is thread 2");
    prom.set_value(10);
    log("thread 2 exits");

    std::lock_guard lock(mtx);
    ++workers_finished;
    cv.notify_one(); // Signal main thread
}


int main()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    // Fire the 2 threads:
    std::thread t1(worker1, std::move(fut));
    std::thread t2(worker2, std::move(prom));

    t1.detach();
    t2.detach();
    
    {
        std::unique_lock lock(mtx);
        while (workers_finished < 2) {
            cv.wait(lock); // Wait until notified (or spurious wakeup)
        }
    }

    log("main exits");
}

Demo

Now, the output is: (The same)

this is thread 1
this is thread 2
thread 2 exits
thread 1 exits
main exits