1

I am working on a programming task that takes in an n value and uses three threads to print zero, even and odd numbers.

For example, if n = 3, it will print 010203. I encountered a strange behavior that when rvalue is used in the condition check in a for loop, my program just hangs entirely without doing anything. This phenomenon only appears in multithreading program. Thus, I put the entire code here:

#include <functional>  // function
#include <iostream>
#include <mutex>   // mutex
#include <thread>  // thread

using namespace std;

class ZeroEvenOdd {
  private:
    int n;
    mutex zero_mtx, even_mtx, odd_mtx;

  public:
    ZeroEvenOdd(int n) {
        this->n = n;
        zero_mtx.unlock();

        even_mtx.lock();
        odd_mtx.lock();
    }

    // printNumber(x) outputs "x", where x is an integer.
    void zero(function<void(int)> printNumber) {
        for (int i = 1; i <= this->n; ++i) {
            zero_mtx.lock();

            printNumber(0);

            if (i & 1) {  // odd
                odd_mtx.unlock();

            } else {  // even
                even_mtx.unlock();
            }
        }
    }

    void even(function<void(int)> printNumber) {
        for (int a = 2; a <= ((this->n) + 1); a += 2) { // NOTE: `(this->n) + 1` is an rvalue
            even_mtx.lock();

            printNumber(a);

            zero_mtx.unlock();
        }
    }

    void odd(function<void(int)> printNumber) {
        for (int b = 1; b <= ((this->n) + 1); b += 2) { // NOTE: `(this->n) + 1` is an rvalue
            odd_mtx.lock();

            printNumber(b);

            zero_mtx.unlock();
        }
    }
};

// g++ 1116.cpp -std=c++11 -pthread -O0 && ./a.out
int main() {
    printf("helloworld\n");

    ZeroEvenOdd zod(3);

    auto printNumber = [](int x) { cout << x; };

    thread threadA([&]() { zod.zero(printNumber); });
    thread threadB([&]() { zod.even(printNumber); });
    thread threadC([&]() { zod.odd(printNumber); });

    threadA.join();
    threadB.join();
    threadC.join();

    return 0;
}

The above program can be compiled and run with g++ 1116.cpp -std=c++11 -pthread -O0 && ./a.out, but the program just hangs without doing anything. However, if I change the lines

for (int a = 2; a <= ((this->n) + 1); a += 2) { // NOTE: `(this->n) + 1` is an rvalue
for (int b = 1; b <= ((this->n) + 1); b += 2) { // NOTE: `(this->n) + 1` is an rvalue

to

for (int a = 2; a <= ((this->n) + 0); a += 2) {
for (int b = 1; b <= ((this->n) + 0); b += 2) {

then everything works as expected.

I've tried to reproduce this phenomenon in single thread program, but this behavior doesn't present in single thread program.

I am not sure if this behavior has anything to do with multithreading. I just provide every evidence I have for people to investigate.

Update: Change typo lvalue to rvalue.

2
  • 3
    You may not unlock() a mutex that wasn't lock()ed in the current thread. Commented Dec 19, 2023 at 7:15
  • 3
    From docs: Unlock: The mutex must be locked by the current thread of execution, otherwise, the behavior is undefined. Prefer a shared mutex. Commented Dec 19, 2023 at 7:27

1 Answer 1

1

Although locking and unlocking of the mutexes in different threads is an issue, it is not the issue.

When the loop control is <= ((this->n) + 1), the program proceeds as follows:

  1. parent locks even and locks odd;
  2. threadB waits for even;
  3. threadC waits for odd;
  4. threadA locks zero, emits '0', unlocks odd and waits for zero;
  5. threadC locks odd, emits '1', unlocks zero and waits for zero;
  6. threadA locks zero, emits '0', unlocks even and waits for zero;
  7. threadB locks even, emits '2', unlocks zero and waits for even;
  8. threadA locks zero, emits '0', unlocks zero and exits;
  9. threadC locks odd, emits '3', unlocks zero and exits.

threadB never exits and waits for ever because (a += 2) == 4 and ((this->n) + 1) == 4.

Nothing is emitted because the output remains buffered.

Sign up to request clarification or add additional context in comments.

4 Comments

If it's the case, it will still at least print something (like 0) instead of nothing.
You are mistaken. By default, cout only physically emits data when it processes a new line character. You can override this default behavior and emit the characters one-by-one by changing printNumber to: auto printNumber = [](int x) { cout << x; cout.flush(); }; If you do this, the output will become: helloworld 010203 And then the program will hang and not return control to the shell.
The difference in behavior of the two original programs is entirely explained by threadB executing two iterations of the "for" loop when the condition is a <= ((this->n) + 1) as compared to a single iteration of the loop when the condition is a <= ((this->n) + 0).
I appreciate all the comments about lock() and unlock() of mutex. As @Jonno explains, there is a bug in my code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.