Состояние резьбы и прохождение по значению в C - PullRequest
0 голосов
/ 08 января 2019

Я не могу понять, почему этот простой сетевой код предотвращает состояние гонки

int main(void) {
   ...
   listen(sd, SOMAXCONN);

     for(;;) {
              pthread_t t; int *socket;

              socket = malloc(sizeof(int));
              *socket = accept(sd, NULL, NULL);

              pthread_create(&t, NULL, service_request, socket);
              pthread_detached(&t);
     }
  ...
}

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

int main(void) {
   ...
   listen(sd, SOMAXCONN);

     for(;;) {
              pthread_t t; int socket;

              socket = accept(sd, NULL, NULL);

              pthread_create(&t, NULL, service_request, socket);
              pthread_detached(&t);
     }
  ...
}

, где я передаю дескриптор сокета "socket" по значению. В любом случае следующее принятие произойдет после того, как pthread_create() уже был вызван и скопировано значение "socket", в другой итерации цикла for.

Теперь я не уверен, копируется ли значение при вызове процедуры pthread_create() в main() или в другой момент. Во втором случае я бы понял состояние гонки.

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Вы правы, вам не нужно динамически выделять (используя, например, malloc) значение для передачи.

Однако функции потока (и pthread_create) принимают указатель , а не значение, и это является причиной большинства проблем, так как многие просто используйте оператор адреса &, чтобы передать указатель на переменную:

pthread_create(&t, NULL, service_request, &socket);

Это действительно может привести к условиям гонки, поскольку значение socket может измениться до того, как функция потока разыменует указатель и использует значение.

Но для простых целых чисел (например, переменной socket в вашем примере) есть простой обходной путь, который не включает использование malloc: приведение значения к указателю. На самом деле, это один из немногих случаев, когда считается приемлемым делать такой кастинг:

pthread_create(&t, NULL, service_request, (void *) (intptr_t) socket);

В функции потока вы выполняете противоположное приведение:

void *service_request(void *pointer)
{
    int socket = (int) (intptr_t) pointer;
    ...
}
0 голосов
/ 08 января 2019

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

Так что malloc() -версия предпочтительнее, как показывает первый пример.

Что они имели в виду под «условием гонки», так это версия:

 for(;;) {
          pthread_t t; int socket;

          socket = accept(sd, NULL, NULL);

          pthread_create(&t, NULL, service_request, &socket);
          pthread_detached(&t);
 }

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...