Почему деструктор, переданный через pthread_key_create, вызывается только дочерним потоком, но основным потоком? - PullRequest
0 голосов
/ 19 марта 2020

Код:

http://man7.org/tlpi/code/online/book/threads/strerror_tsd.c

static void                         /* Free thread-specific data buffer */
destructor(void *buf)
{
    free(buf);
}

static void                         /* One-time key creation function */
createKey(void)
{
    int s;

    /* Allocate a unique thread-specific data key and save the address
       of the destructor for thread-specific data buffers */

    s = pthread_key_create(&strerrorKey, destructor);
    if (s != 0)
        errExitEN(s, "pthread_key_create");
}

char *
strerror(int err)
{
    int s;
    char *buf;

    /* Make first caller allocate key for thread-specific data */

    s = pthread_once(&once, createKey);
    if (s != 0)
        errExitEN(s, "pthread_once");

    buf = pthread_getspecific(strerrorKey);
    if (buf == NULL) {          /* If first call from this thread, allocate
                                   buffer for thread, and save its location */
        buf = malloc(MAX_ERROR_LEN);
        if (buf == NULL)
            errExit("malloc");

        s = pthread_setspecific(strerrorKey, buf);
        if (s != 0)
            errExitEN(s, "pthread_setspecific");
    }

    if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
        snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
    } else {
        strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
        buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
    }

    return buf;
}

http://man7.org/tlpi/code/online/dist/threads/strerror_test.c

static void *
threadFunc(void *arg)
{
    char *str;

    printf("Other thread about to call strerror()\n");
    str = strerror(EPERM);
    printf("Other thread: str (%p) = %s\n", str, str);

    return NULL;
}

int
main(int argc, char *argv[])
{
    pthread_t t;
    int s;
    char *str;

    str = strerror(EINVAL);
    printf("Main thread has called strerror()\n");

    s = pthread_create(&t, NULL, threadFunc, NULL);
    if (s != 0)
        errExitEN(s, "pthread_create");

    s = pthread_join(t, NULL);
    if (s != 0)
        errExitEN(s, "pthread_join");

    /* If strerror() is not thread-safe, then the output of this printf() be
       the same as that produced by the analogous printf() in threadFunc() */

    printf("Main thread:  str (%p) = %s\n", str, str);

    exit(EXIT_SUCCESS);
}

Вопрос:

int pthread_key_create(pthread_key_t *key, void (destructor)(void *));

Реализация strerror использует данные потока c. После завершения потока, имеющего ненулевое значение, связанное с ключом, функция деструктора автоматически вызывается API Pthreads и получает это значение в качестве аргумента. Основываясь на моем тестировании, основной поток, вызывающий strerror, НЕ будет запускать destructor, но дочерний поток будет. Хотелось бы узнать почему?

Спасибо

1 Ответ

3 голосов
/ 19 марта 2020

Вот упрощенный пример:

#include <pthread.h>
#include <stdio.h>

static pthread_key_t key;

void destroy(void *data) 
{
  fprintf(stderr, "==> %s on thread %lu with argument %p\n", __func__, pthread_self(), data);
}

void *target(void *data)
{
  // comment out the following line and destroy will not be called from child thread
  pthread_setspecific(key, target);

  fprintf(stderr, "%s set key to %p on thread %lu\n", __func__, pthread_getspecific(key), pthread_self());
  return NULL;
}

int main(int argc, char *argv[])
{
  fprintf(stderr, "%s is on thread %lu\n", __func__, pthread_self());

  pthread_key_create(&key, destroy);
  pthread_setspecific(key, main);

  fprintf(stderr, "%s set key to %p\n", __func__, pthread_getspecific(key));

  pthread_t child;
  pthread_create(&child, NULL, target, NULL);

  fprintf(stderr, "main thread created thread %lu\n",  child);

  pthread_join(child, NULL);

  fprintf(stderr, "main thread joined child\n");

  // comment out the following line and destroy will not be called from main thread
  pthread_exit(main);
  return 0;
}

Согласно справочной странице для pthread_key_create

При выходе из потока, если значение ключа имеет указатель деструктора, отличный от NULL, и поток имеет ненулевое значение, связанное с этим ключом, значение ключа устанавливается в NULL, а затем вызываемая функция вызывается с ранее связанным значением в качестве единственного аргумента.

Как в коде вопроса, так и в упрощенном примере каждый поток установил для ключа значение, отличное от NULL, поэтому кажется, что деструктор должен вызываться в каждом потоке.

Но при выходе из основного потока по умолчанию ключ, определяющий c деструкторов, не вызывается. См. pthread_create справочную страницу и вопрос 24521968 . Вызов pthread_exit () в конце main вызовет деструктор.

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