c++ - How is it possible that I'm locking a mutex multiple times, if mutex shall be possible to be locked only once? - S

I'm trying to understand c++ semaphore and mutex, and I found out that I'm locking 1 mutex se

I'm trying to understand c++ semaphore and mutex, and I found out that I'm locking 1 mutex several times, or at least my debugging messages are showing that is the case. Even though we have only 1 mutex in semaphore, I'm aquiring it for more than 1 thread. I understand that semaphore is supposed to allow the access by mutltiple threads (limited by the counter), but internally semaphore is made of mutex that can have value 0 or 1, taken or not taken. In my opinion it shall be not possible that 2 threads will lock the mutex 1 after another, but I can see that in the log file

/**
 * Connecting cell phones to a charger
 */
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <cstdio>
#include <bits/stdc++.h>

unsigned int data_race = 0;

class Semaphore {
public:
    Semaphore(unsigned long init_count) {
        count_ = init_count;
    }

    void acquire(int id) { // decrement the internal counter
        printf("%d wants to aquire.\n", id);
        std::unique_lock<std::mutex> lck(m_);
        printf("%d aquired mutex in aquire\n", id);
        while (!count_) {
            printf("%d is waiting for lock\n", id);
            cv_.wait(lck);
        }
        printf("%d is about to decrease count\n", id);
        count_--;
    }

    void release(int id) { // increment the internal counter
        printf("%d is about to relase \n", id);
        std::unique_lock<std::mutex> lck(m_);
        printf("%d acuired mutex in release\n", id);
        count_++;
        lck.unlock();
       cv_.notify_one();
    }

private:
    std::mutex m_;
    std::condition_variable cv_;
    unsigned long count_;
};

Semaphore charger(2);

void cell_phone(int id) {
    charger.acquire(id);
    printf("Phone %d is charging...\n", id);
    srand(id); // charge for "random" amount between 1-3 seconds
    std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 2000 + 1000));
    printf("Phone %d is DONE charging!\n", id);
    charger.release(id);
}

int main() {
    std::thread phones[6];
    for (int i=0; i<6; i++) {
        phones[i] = std::thread(cell_phone, i);
    }
    for (auto& p : phones) {
        p.join();
    }
}

It's compiled with c++17, I thought that only 1 mutex will be aquired at once in this implemenation.

The result:

PS C:\HHardDrive\embedded C\output> & .\'Semaphore_CriticalSection2.exe'
0 wants to aquire.
0 aquired mutex in aquire    //->Mutex locked first time
0 is about to decrease count
Phone 0 is charging...      
4 wants to aquire.
4 aquired mutex in aquire    //->Mutex locked second time
4 is about to decrease count
Phone 4 is charging...      
3 wants to aquire.
3 aquired mutex in aquire   
3 is waiting for lock       
2 wants to aquire.
2 aquired mutex in aquire   
2 is waiting for lock       
5 wants to aquire.
5 aquired mutex in aquire
1 wants to aquire.
5 is waiting for lock
1 aquired mutex in aquire
1 is waiting for lock
Phone 0 is DONE charging!
0 is about to relase 
0 acuired mutex in release
Phone 4 is DONE charging!
3 is about to decrease count
4 is about to relase
4 acuired mutex in release
Phone 3 is charging...
2 is about to decrease count
Phone 2 is charging...
Phone 2 is DONE charging!
2 is about to relase 
Phone 3 is DONE charging!
2 acuired mutex in release
3 is about to relase
5 is about to decrease count
Phone 5 is charging...
3 acuired mutex in release
1 is about to decrease count
Phone 1 is charging...
Phone 1 is DONE charging!
Phone 5 is DONE charging!
1 is about to relase
5 is about to relase 
1 acuired mutex in release
5 acuired mutex in release

I'm trying to understand c++ semaphore and mutex, and I found out that I'm locking 1 mutex several times, or at least my debugging messages are showing that is the case. Even though we have only 1 mutex in semaphore, I'm aquiring it for more than 1 thread. I understand that semaphore is supposed to allow the access by mutltiple threads (limited by the counter), but internally semaphore is made of mutex that can have value 0 or 1, taken or not taken. In my opinion it shall be not possible that 2 threads will lock the mutex 1 after another, but I can see that in the log file

/**
 * Connecting cell phones to a charger
 */
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <cstdio>
#include <bits/stdc++.h>

unsigned int data_race = 0;

class Semaphore {
public:
    Semaphore(unsigned long init_count) {
        count_ = init_count;
    }

    void acquire(int id) { // decrement the internal counter
        printf("%d wants to aquire.\n", id);
        std::unique_lock<std::mutex> lck(m_);
        printf("%d aquired mutex in aquire\n", id);
        while (!count_) {
            printf("%d is waiting for lock\n", id);
            cv_.wait(lck);
        }
        printf("%d is about to decrease count\n", id);
        count_--;
    }

    void release(int id) { // increment the internal counter
        printf("%d is about to relase \n", id);
        std::unique_lock<std::mutex> lck(m_);
        printf("%d acuired mutex in release\n", id);
        count_++;
        lck.unlock();
       cv_.notify_one();
    }

private:
    std::mutex m_;
    std::condition_variable cv_;
    unsigned long count_;
};

Semaphore charger(2);

void cell_phone(int id) {
    charger.acquire(id);
    printf("Phone %d is charging...\n", id);
    srand(id); // charge for "random" amount between 1-3 seconds
    std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 2000 + 1000));
    printf("Phone %d is DONE charging!\n", id);
    charger.release(id);
}

int main() {
    std::thread phones[6];
    for (int i=0; i<6; i++) {
        phones[i] = std::thread(cell_phone, i);
    }
    for (auto& p : phones) {
        p.join();
    }
}

It's compiled with c++17, I thought that only 1 mutex will be aquired at once in this implemenation.

The result:

PS C:\HHardDrive\embedded C\output> & .\'Semaphore_CriticalSection2.exe'
0 wants to aquire.
0 aquired mutex in aquire    //->Mutex locked first time
0 is about to decrease count
Phone 0 is charging...      
4 wants to aquire.
4 aquired mutex in aquire    //->Mutex locked second time
4 is about to decrease count
Phone 4 is charging...      
3 wants to aquire.
3 aquired mutex in aquire   
3 is waiting for lock       
2 wants to aquire.
2 aquired mutex in aquire   
2 is waiting for lock       
5 wants to aquire.
5 aquired mutex in aquire
1 wants to aquire.
5 is waiting for lock
1 aquired mutex in aquire
1 is waiting for lock
Phone 0 is DONE charging!
0 is about to relase 
0 acuired mutex in release
Phone 4 is DONE charging!
3 is about to decrease count
4 is about to relase
4 acuired mutex in release
Phone 3 is charging...
2 is about to decrease count
Phone 2 is charging...
Phone 2 is DONE charging!
2 is about to relase 
Phone 3 is DONE charging!
2 acuired mutex in release
3 is about to relase
5 is about to decrease count
Phone 5 is charging...
3 acuired mutex in release
1 is about to decrease count
Phone 1 is charging...
Phone 1 is DONE charging!
Phone 5 is DONE charging!
1 is about to relase
5 is about to relase 
1 acuired mutex in release
5 acuired mutex in release
Share Improve this question edited Nov 16, 2024 at 17:30 Daniel Dobiński asked Nov 16, 2024 at 15:36 Daniel DobińskiDaniel Dobiński 32 bronze badges 15
  • 1 When you wait on a condition variable the mutex is released during the time the thread sleeps (and is re-acquired for checking the condition). – wohlstad Commented Nov 16, 2024 at 15:39
  • 4 A side note: Why should I not #include <bits/stdc++.h>?. – wohlstad Commented Nov 16, 2024 at 15:39
  • 2 Also : NEVER wait on a condition variable without a predicate! Condition variables can have what is called "spurious wakeups" (something C++ cannot help, it is an OS thing). S – Pepijn Kramer Commented Nov 16, 2024 at 16:27
  • 2 @PepijnKramer never say never. calling wait from a while loop (as they do here) is exactly equivalent to using a predicate – Alan Birtles Commented Nov 16, 2024 at 16:33
  • 1 Two hints: First, if you want to discuss about locking mutexes only, please make your example more simple, without srand, just two concurrent threads, clear timings etc. Second, add timestamps to your debug messages, because you shouldn't rely on the sequence of stdout, only. – Markus Commented Nov 16, 2024 at 18:00
 |  Show 10 more comments

1 Answer 1

Reset to default 0

Thread 0 unlocked the mutex in acquire() in the destructor of std::unique_lock<std::mutex> lck, which executed upon exiting the block where it was defined. This happened just before acquire returned, and shortly after the message "0 is about to decrease count" was printed. So thread 0 had indeed unlocked the lock before thread 4 locked it.

This RAII pattern is the whole point of unique_lock; it locks the mutex at the point where the unique_lock is defined (i.e. where its constructor runs), holds the lock from there until the unique_lock goes out of scope (normally the end of the enclosing block), and then unlocks it in the destructor.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745654364a4638455.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信