简体   繁体   中英

C/C++ - Single semaphore of type sem_t to print numbers in order

Problem: Let's say we have n threads where each thread receives a random unique number between 1 and n. And we want the threads to print the numbers in sorted order.

Trivial Solution (using n semaphore/mutex): We can use n mutex locks (or similarly semaphores) where thread i waits to acquire mutex lock number i and unlocks number i + 1. Also, thread 1 has no wait.

However, I'm wondering if it's possible to simulate a similar logic using a single semaphore (of type sem_t) to implement the following logic: (i is a number between 1 to n inclusive)

Thread with number i as input, waits to acquire a count of (i-1) on the semaphore, and after printing, releases a count of i. Needless to say, thread one does not wait.

I know that unlike Java, sem_t does not support arbitrary increase/decrease in the semaphore value. Moreover, writing a for loop to do (i-1) wait and i release won't work because of asynchrony.

I've been looking for the answer for so long but couldn't find any. Is this possible in plain C? If not, is it possible in C++ using only one variable or semaphore? Overall, what is the least wasteful way to do this with ONE semaphore.

Please feel free to edit the question since I'm new to multi-threaded programming.

Thats a good question although, I fear you might have a XY problem since I can not imagine a good reason for your problem scenario. Never the less, after 1-2 minutes I came up with 2 solutions with pros and cons, but I think one is perfect for you:

A. When your threads are almost done the same time and or need their print ASAP you could use a shared std::atomic<T> with T=unsigned,int,size_t,uint32_t what ever you like, or any of the integer atomics in the C standard library when using C, initialise it with 0, and now every thread i busy waits until its value is i-1. If so, it prints and then adds 1 on the atomic. Of course since of the busy wait, you will have much CPU load when thread are waiting long, and slow down, when many are waiting. But you get your print ASAP

B. You just store your result of thread i in a container, maybe along with its index, since I guess you want more to just print i, and after all threads are finished or periodically, sort this container and then print it.

A.:

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <functional>

void thread_function(unsigned i, std::atomic<unsigned>& atomic) {
    while (atomic < i - 1) {}
    std::cout << i << " ";
    atomic += 1;
}

int main() {
    std::atomic<unsigned> atomic = 0;

    std::vector<std::thread> threads;
    for (auto i : {3,1,2}) {
        threads.push_back(std::thread(thread_function, i, std::ref(atomic)));
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "\n";
}

Works also in C, just use the atomics there.

You can do this with a condition_variable in C++, which is equivalent to a pthread_cond_t with the pthreads library in C.

What you want to share between threads is a pointer to a condition_variable, number, and a mutex to guard access to the number.

struct GlobalData
{
    std::condition_variable cv;
    int currentValue;
    std::mutex mut;
};

Each thread simply invokes a function that waits for its number to be set:

void WaitForMyNumber(std::shared_ptr<GlobalData> gd, int number)
{
    std::unique_lock<std::mutex> lock(gd->mut);
    while (gd->currentValue != number)
    {
        gd->cv.wait(lock);
    }

    std::cout << number << std::endl;
    gd->currentValue++;
    gd->cv.notify_all(); // notify all other threads that it can wake up and check
}

And then a program to test it all out. This one uses 10 threads. You can modify it to use more and then have your own randomization algorithm of the numbers list.

int main()
{
    int numbers[10] = { 9, 1, 0, 7, 5, 3, 2, 8, 6, 4 };
    std::shared_ptr<GlobalData> gd = std::make_shared<GlobalData>();
    // gd->number is initialized to 0.

    std::thread threads[10];

    for (int i = 0; i < 10; i++)
    {
        int num = numbers[i];
        auto fn = [gd, num] {WaitForMyNumber(gd, num); };
        threads[i] = std::move(std::thread(fn));
    }

    // wait for all the threads to finish
    for (int i = 0; i < 10; i++)
    {
        threads[i].join();
    }

    return 0;
}

All of the above is in C++. But it would be easy to transpose the above solution to C using pthreads . But I'll leave that as an exercise for the OP.

I'm not sure if this satisfies your "one semaphore requirement". The mutex technically has a semaphore. Not sure if the condition_variable itself has a semaphore for its implementation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM