Простое решение для вызова функции только в одном потоке, очереди ожидания?

Предположим, что существует функция EventHandler, которая вызывается в нескольких потоках в разные моменты времени.

EventHandler должен вызывать SomeOtherFunction , , но эти вызовы должны выполняться только по одному потоку за раз.

Это означает: если вызов SomeOtherFunction в настоящее время активен в потоке A и EventHandler снова вызывается (в другом потоке B), тогда SomeOtherFunction не следует вызывать на потоке B, но SomeOtherFunction вызывается, когда он возвращается в потоке A.

Что-то вроде этого (псевдокод):

function EventHandler()
{
  numberPendingCallsSemaphore.Release();    // increase
  if (mayCallSemaphore.Acquire())           // (i)
  {
    while (numberPendingCallsSemaphore.Acquire())  // decrease
    {
      SomeOtherFunction();
    }
    // (ii)
    mayCallSemaphore.Release();
  }
}

Конечно, этот код работает not из-за гонки: один поток находится в позиции (ii) с помощью mayCallSemaphore, а другой в позиции (i) не может принимать mayCallSemaphore.

Я написал рабочий код с помощью блокировки, но код довольно длинный и уродливый. С другой стороны, эта проблема выглядит довольно просто с первого взгляда, таким образом:

Есть ли простое, потенциально простое решение для этого, которое я не смог найти или сейчас не вижу?

3 голоса | спросил Martin 27 FebruaryEurope/MoscowbTue, 27 Feb 2018 17:20:59 +0300000000pmTue, 27 Feb 2018 17:20:59 +030018 2018, 17:20:59

1 ответ


3

Ваше предлагаемое решение имеет два объекта управления. mayCallSemaphore выглядит как Mutex на основе того, как вы его используете, и numberPendingCallsSemaphore представляется как Условие Переменная . У вас это почти правильно, но, как вы догадались, наличие двух управляющих структур, которые работают независимо, приводит к условиям гонки, тупиковой ситуации или тому и другому в зависимости от топологии и ситуации. Возможны также потерянные пробуждения и другие вещи.

Если вы внимательно посмотрите на это GitHub документацию о том, как построить счетный семафор, используя как Mutex, так и переменную условия , вы увидите, что CV фактически использует (взаимодействует с) Mutex внутри, поэтому не может быть никаких рас или взаимоблокировок. Я скопирую и вставляю примерное решение прямо здесь для удобства:

typedef struct sem_t {
  int count; 
  pthread_mutex_t m;
  pthread_condition_t cv;
} sem_t;

int sem_init(sem_t *s, int pshared, int value) {
  if (pshared) { errno = ENOSYS /* 'Not implemented'*/; return -1;}

  s->count = value;
  pthread_mutex_init(&s->m, NULL);
  pthread_cond_init(&s->cv, NULL);
  return 0;
}

sem_post(sem_t *s) {
  pthread_mutex_lock(&s->m);
  s->count++;
  pthread_cond_signal(&s->cv); /* See note */
  /* A woken thread must acquire the lock, so it will also have to wait until we call unlock*/

  pthread_mutex_unlock(&s->m);
}

sem_wait(sem_t *s) {
  pthread_mutex_lock(&s->m);
  while (s->count == 0) {
      pthread_cond_wait(&s->cv, &s->m); /*unlock mutex, wait, relock mutex*/
  }
  s->count--;
  pthread_mutex_unlock(&s->m);
}

Просто защитите свой вызов до SomeOtherFunction(), вызвав sem_wait() до и затем вызывать sem_post() после. Внутренние методы будут позаботиться обо всех деталях, что приведет к одновременному доступу к блоку, что позволит выполнять только одно критическое разделение вашего кода за раз. Все это основано на примитивах Posix, которые являются частью ОС и не могут пойти не так. В вашем случае счет должен быть ограничен одним, поэтому вы можете либо упростить этот код, либо инициализировать семафор с помощью 1 и позже использовать другой код для другой ситуации.

Я использовал этот же подход, взятый из страниц руководства POSIX (около 20 лет назад) - для создания многопоточного многопроцессорного сервера транзакций базы данных до того, как веб-сервер Apache был многопоточным. У меня был один основной поток, который принимает входящие TCP-соединения и создает до «N» рабочих потоков за раз (переходный, а не пул). Он долгое время работал на очень большом сайте со многими пользователями. Unix предоставляет вам инструменты для создания правильных решений, вам просто нужно собрать их вместе.

ответил 1 MarpmThu, 01 Mar 2018 17:28:24 +03002018-03-01T17:28:24+03:0005 2018, 17:28:24

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132