Search code examples
cmultithreadingthread-safetythread-local-storage

Why must __thread follow extern or static


I'm reading Kerrisk's book and see that the following as a note on 31-4,

  • The __thread keyword must immediately follow the static or extern keyword, if either of these is specified in the variable’s declaration.
  • The declaration of a thread-local variable can include an initializer, in the same manner as a normal global or static variable declaration.
  • The C address (&) operator can be used to obtain the address of a thread-local variable.

I wonder the reason of the fact that the keyword must come behind static or extern. Can't it be used without any of them?

Its example code,

/*************************************************************************\
*                  Copyright (C) Michael Kerrisk, 2018.                   *
*                                                                         *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the   *
* Free Software Foundation, either version 3 or (at your option) any      *
* later version. This program is distributed without any warranty.  See   *
* the file COPYING.gpl-v3 for details.                                    *
\*************************************************************************/

/* strerror_tls.c

   An implementation of strerror() that is made thread-safe through
   the use of thread-local storage.

   See also strerror_tsd.c.

   Thread-local storage requires: Linux 2.6 or later, NPTL, and
   gcc 3.3 or later.
*/
#define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
                                       declarations from <stdio.h> */
#include <stdio.h>
#include <string.h>                 /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"


#define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
                                       buffer returned by strerror() */
/*   ||||||||||||||||||
//   vvvvvvvvvvvvvvvvvv
*/
    static     __thread      char buf[MAX_ERROR_LEN];
/* Thread-local return buffer */


char *
strerror(int err)
{
    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;
}

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);
}

Solution

  • The standard C storage class specifier for thread local variables is _Thread_local. The standard also says in §6.11 Future directions:

    6.11.5 Storage class specifiers

    The placement of a storage-class specifier other than at the beginning of the declaration specifiers in a declaration is an obsolescent feature.

    Thus, the standard says that storage class keywords (static, extern, auto — don't use it! — register — ditto — _Thread_local and typedef) should appear at the start of a declaration. Where either static or extern appears and _Thread_local, the recommendation from the book is that static or extern should be first and _Thread_local second.

    Of course, the book is using __thread and not _Thread_local. That's a compiler (implementation) specific keyword that behaves similarly to the standard C _Thread_local and Microsoft's __declspec(thread).

    The GCC documentation on thread local storage documents (emphasis added):

    At the user level, the extension is visible with a new storage class keyword: __thread. For example:

    __thread int i;
    extern __thread struct state s;
    static __thread char *p;
    

    The __thread specifier may be used alone, with the extern or static specifiers, but with no other storage class specifier. When used with extern or static, __thread must appear immediately after the other storage class specifier.

    The __thread specifier may be applied to any global, file-scoped static, function-scoped static, or static data member of a class. It may not be applied to block-scoped automatic or non-static data member.

    So, what you're seeing is the GCC-specific notation for thread-local storage, and as I noted and as the GCC manual notes, storage class information should come first in a declaration (and GCC explicitly says __thread after static or extern).

    See also Common variable attributes.