Как определить, когда все экземпляры потока A завершились из потока B

#c #multithreading #concurrency

Вопрос:

В настоящее время я создаю программу на C , которая имитирует официантов и клиентов в ресторане с использованием потоков. Программа запускает 40 потоков клиентов и 3 потока официантов с такими функциями:

 void *customer(void * vargp){
    //depending on assigned table, call waiter 1-3 using semaphore
    sem_post(amp;callWaiter);
    //wait for waiter to respond
    sem_wait(amp;waiterResponse);
    //leave table and finish thread
}

void *waiter(void *vargp){
    //this is what I'm trying to fix
    while(there are still customers){ //this check has been a number of different attempts
        sem_wait(amp;callWaiter);
        sem_post(amp;waiterResponse);
    }
}
 

Проблема, для которой я пытаюсь найти решение, заключается в том, что официант увидит, что клиент все еще активен (с помощью нескольких различных методов, которые я пробовал), затем заблокирует ожидание семафора, затем клиент завершит работу, и клиентов больше не будет. Есть ли у кого-нибудь идеи о том, как поток официантов может проверять наличие запущенных клиентов без прохождения проверки до завершения последних клиентов?

Комментарии:

1. Это похоже на программу на C, использующую библиотеку C pthread напрямую. Если это действительно C , почему бы не использовать стандартные C std::thread и их вспомогательные функции и классы?

2. Что делать, если остался только один клиент, и все три официанта звонят sem_wait() одновременно? Общий подход в корне ошибочен.

3. Дополнение к комментарию Теда. Поскольку вы сказали, что «используете» и, вероятно, компилируете с C , используйте решение на C 11 (или выше). Это будет намного проще. Посмотрите на такие вещи, как <thread> , <atomic>, <mutex>, <memory> .

4. Прошу прощения, я должен был уточнить. Я запускаю эту программу на школьном компьютере с Linux (в соответствии с инструкциями), он использует c 98

5. Рекомендация: Дайте преподавателю то, что он хочет, но дополните свое обучение современными материалами, чтобы облегчить переход к рабочей силе.

Ответ №1:

То, что вы ищете, — это переменные условия. Они находятся в pthreads и теперь являются частью библиотеки потоков C 11. Они специально созданы для решения проблемы, с которой вы столкнулись, потому что проблема, с которой вы столкнулись, не может быть решена в целом с помощью мьютексов и швов.

Однако конкретная версия, которая вам нужна, может быть разрешимой. Я не знаю, в чем заключается ваша конкретная проблема с домашним заданием, но она может быть очень похожа на проблему со спящим парикмахером, у которой есть несколько известных решений, по крайней мере, одно из которых находится по ссылке в Википедии выше.

Комментарии:

1. Спасибо за ссылку на эту статью. Я изучу это подробнее, чтобы узнать, есть ли у него решение, которое мне нужно

Ответ №2:

С семафорами простым способом было бы использовать sem_getvalue() семафор, инициализированный количеством клиентов.

 int num_customers(bool = false){
    int v;
    sem_getvalue(amp;customers, amp;v);
    return v;
}
 

Когда клиенты уходят, каждый выполняет sem_wait() работу с этим семафором. Когда sem_getvalue() возвращается 0, это означает, что все клиенты ушли.

Хитрость заключалась бы в том, чтобы последний клиент разбудил своего официанта, чтобы сообщить им, что они ушли.

     //leave table and finish thread
    sem_wait(amp;customers);
    if (num_customers() == 0) sem_post(amp;callWaiter);
 

Официанты сначала проверяют, есть ли клиенты, прежде чем обращаться к клиенту.

     while(num_customers(true) > 0){

        //wait for work
        sem_wait(amp;callWaiter);
        if (num_customers(true) == 0) break;

        //work
        sem_post(amp;waiterResponse);
    }
 

Трюк последнего клиента, разбудившего официанта, имеет безвредную гонку, в которой несколько клиентов могут считать, что они последние, и поэтому официант получает несколько сообщений, каждое из которых указывает, что клиентов больше нет. Эти дополнительные сообщения безвредны, поскольку официант все равно выходит. Однако избежать этой гонки можно с помощью другого семафора, инициализированного значением 1, используемого для того, чтобы первый клиент, который не обнаружил больше клиентов, мог разбудить официанта.

 int num_customers(bool is_waiter = false){
    int v;
    sem_getvalue(amp;customers, amp;v);
    if (is_waiter) return v;
    if (v == 0) {
        if (sem_trywait(amp;last_customer) == -1) {
            assert(errno == EAGAIN);
            return 1;
        }
    }
    return v;
}
 

Комментарии:

1. Спасибо за ваш ответ. Ваше решение использовать безвредное условие гонки было именно тем, что мне было нужно

Ответ №3:

Для простых состояний можно использовать атомарные переменные:

 //  waiter
myId= waiter id
while(work)
{
    work=false;
    for(int n=0;n<40;n  )
    while (customerWaiterMatrix[n][myId].load()>0) // one of 120 atomic variables
    {
           work=true;
           std::lock_guard<std::mutex>(mutexOfThatCustomerThisWaiter);
           // thread safe customer logic
    }
}
 

Где это можно добавить следующим образом:

 // customer 
myId = customer id
customerWaiterMatrix[myId][selectedWaiterId].fetch_add(1);
same lock of current customer amp; waiter with lock guard
Do safe logic
customerWaiterMatrix[myId][selectedWaiterId].fetch_add(-1);
 

Конфликт блокировок уменьшается, нет никакой двусмысленности в отношении того, что происходит после чего.

Комментарии:

1. Я всегда с подозрением отношусь к таким вещам, как if (atomic_read) { lock mutex and do stuff } . Атомарное чтение безопасно, но ничто не мешает другому потоку проникнуть между чтением и блокировкой мьютекса

2. Лучше иметь циклический массив и атомарные приращения чтения и записи, подобные параллельной очереди, но с распределением атомарного индекса, чтобы официанты не влияли друг на друга.

3. И он говорит, что конкретно вызывает 1 официанта от клиента. Таким образом, официанты не пересекают пути друг друга. Но вы правы, если это был бесплатный сервис для всех. Я имею в виду, что существует 40×3 = 120 атомарных переменных, каждая из которых независима.