1

well, actually, I'm not asking the threads must "line up" to work, but I just want to notify multiple threads. so I'm not looking for barrier.

it's kind of like the condition_variable::notify_all(), but I don't want the threads wakeup one-by-one, which may cause starvation(also the potential problem in multiple semaphore post operation). it's kind of like:

std::atomic_flag flag{ATOMIC_FLAG_INIT};
void example() {
    if (!flag.test_and_set()) {
//  this is the thread to do the job, and notify others
        do_something();
        notify_others(); // this is what I'm looking for
        flag.clear();
    } else {
//  this is the waiting thread
        wait_till_notification();
        do_some_other_thing();
    }
}

void runner() {
    std::vector<std::threads>;
    for (int i=0; i<10; ++i) {
        threads.emplace_back([]() {
        while(1) {
            example();
        }
    });
    }
// ...
}

so how can I do this in c/c++ or maybe posix API?


sorry, I didn't make this question clear enough, I'd add some more explaination.

it's not thunder heard problem I'm talking about, and yes, it's the re-acquire-lock that bothers me, and I tried shared_mutex, there's still some problem.

let me split the threads to 2 parts, 1 as leader thread, which do the writing job, the others as worker threads, which do the reading job.

but actually they're all equal in programme, the leader thread is the thread that 1st got access to the job( you can take it as the shared buffer is underflowed for this thread). once the job is done, the other workers just need to be notified that them have the access. if the mutex is used here, any thread would block the others. to give an example: the main thread's job do_something() here is a read, and it block the main thread, thus the whole system is blocked.

unfortunatly, shared_mutex won't solve this problem:

void example() {
    if (!flag.test_and_set()) {
    // leader thread:
        lk.lock();
        do_something();
        lk.unlock();
        flag.clear();
    } else {
// worker thread
        lk.shared_lock();
        do_some_other_thing();
        lk.shared_unlock();
    }
}
// outer loop
void looper() {
    std::vector<std::threads>;
    for (int i=0; i<10; ++i) {
        threads.emplace_back([]() {
        while(1) {
            example();
        }
    });
    }
}

in this code, if the leader job was done, and not much to do between this unlock and next lock (remember they're in a loop), it may get the lock again, leave the worker jobs not working, which is why I call it starve earlier.

and to explain the blocking in do_something(), I don't want this part of job takes all my CPU time, even if the leader's job is not ready (no data arrive for read)

and std::call_once may still not be the answer to this. because, as you can see, the workers must wait till the leader's job finished.

to summarize, this is actually a one-producer-multi-consumer problem. but I want the consumers can do the job when the product is ready for them. and any can be the producer or consumer. if any but the 1st find the product has run out, the thread should be the producer, thus others are automatically consumer.

but unfortunately, I'm not sure if this idea would work or not

陈泽霖
  • 95
  • 6
  • 4
    Maybe I just don't know enough about implementation details, but *but I don't want the threads wakeup one-by-one, which may cause starvation* sounds like an assumption. Or maybe you are not explaining what you mean sufficiently enough. – super Nov 17 '21 at 09:59
  • Just do not assume the OS can do magic. OS at the end is a program that is written by merely mortal people so it won't be able to do what you are asking for. – Mahmoud Fayez Nov 17 '21 at 10:03
  • I edited the question to explain myself – 陈泽霖 Nov 17 '21 at 10:04
  • Are you perhaps looking for `std::call_once`? – Quimby Nov 17 '21 at 10:04
  • Potentially related: https://stackoverflow.com/questions/37866976/releasing-multiple-locks-without-causing-priority-inversion – Sneftel Nov 17 '21 at 10:08
  • 1
    "wakeup one-by-one" as opposed to? – n. m. could be an AI Nov 17 '21 at 10:23
  • Please show the outer loop. Currently it looks like you're busy-waiting on an atomic flag, but also doing a blocking operation while holding an exclusive lock? – Useless Nov 18 '21 at 18:51
  • Does your producer generate work for multiple consumers per iteration? Can there be multiple producers at the same time or would they just block each other? Why do the consumer threads need to switch role into producer, why can't you use a normal 1-producer-N-consumer or N-producer-M-consumer queue? How compares runtime cost of production to consumption? – Homer512 Nov 20 '21 at 10:16
  • @Homer512 well, there can't be multiple producers at the same time. I was assuming this would perform better if there would be no extra producer thread to maintain. but as the result tells, I was wrong, the thread mutex costs more. – 陈泽霖 Nov 22 '21 at 01:34

2 Answers2

2

it's kind of like the condition_variable::notify_all(), but I don't want the threads wakeup one-by-one, which may cause starvation

In principle it's not waking up that is serialized, but re-acquiring the lock.

You can avoid that by using std::condition_variable_any with a std::shared_lock - so long as nobody ever gets an exclusive lock on the std::shared_mutex. Alternatively, you can provide your own Lockable type.

Note however that this won't magically allow you to concurrently run more threads than you have cores, or force the scheduler to start them all running in parallel. They'll just be marked as runnable and scheduled as normal - this only fixes the avoidable serialization in your own code.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • thx, this looks like a solution to the problem, with additional lock for main thread. I'll give it a shot. – 陈泽霖 Nov 18 '21 at 03:54
0

It sounds like you are looking for call_once

#include <mutex>

void example()
{
  static std::once_flag flag;
  bool i_did_once = false;
  std::call_once(flag, [&i_did_once]() mutable {
    i_did_once = true;
    do_something();
  });
  if(! i_did_once)
    do_some_other_thing();
}

I don't see how your problem relates to starvation. Are you perhaps thinking about the thundering herd problem? This may arise if do_some_other_thing has a mutex but in that case you have to describe your problem in more detail.

Homer512
  • 9,144
  • 2
  • 8
  • 25