【问题标题】:Why must __thread follow extern or static为什么 __thread 必须遵循 extern 或 static
【发布时间】:2019-02-08 16:40:36
【问题描述】:

我正在阅读 Kerrisk's book 并看到以下内容作为 31-4 的注释,

  • __thread 关键字必须紧跟 staticextern 关键字,如果在变量的 声明。
  • 线程局部变量的声明可以包括初始化程序,其方式与普通全局变量或静态变量相同 声明。
  • C 地址 (&) 运算符可用于获取线程局部变量的地址。

我想知道关键字必须在staticextern后面的原因。没有它们就不能使用吗?

它的示例代码,

/*************************************************************************\
*                  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);
}

【问题讨论】:

  • __thread 甚至是关键字吗?我在搜索 C 标准时没有看到它。这是_Thread_local 的自定义别名吗?
  • 示例代码有很多问题,比如使用_sys_errlist,它已被弃用、不安全,并且引入了对特定版本 glibc程序链接的ABI依赖反对,由于复制重定位混乱。
  • 贴出的代码无法编译!这可以通过头文件的内容来修复:lpi_hdr.h 但是,您的问题中缺少该文件的内容。
  • _Thread_local 是线程局部变量的 C11 / C18 标准表示法。 __thread 符号牢牢地位于“为系统保留”的名称空间中;它可以做任何事情,但功能可能有点像_Thread_local,定义该变量是线程局部变量。它与__declspec(thread) 没有太大区别——既不好也不坏。

标签: c multithreading thread-safety thread-local-storage


【解决方案1】:

线程局部变量的标准 C 存储类说明符是 _Thread_local。该标准还在§6.11 Future directions 中说:

6.11.5 存储类说明符

存储类说明符在声明开头以外的位置 声明中的说明符已过时。

因此,标准规定存储类关键字(staticexternauto — 不要使用它! — register — 同上 — _Thread_localtypedef应该 出现在声明的开头。在出现staticextern_Thread_local 的地方,本书的建议是staticextern 应该是第一个,_Thread_local 第二个。

当然,本书使用的是__thread,而不是_Thread_local。这是一个编译器(实现)特定的关键字,其行为类似于标准 C _Thread_local 和 Microsoft 的 __declspec(thread)

thread local storage 文档上的 GCC 文档(已添加重点):

在用户级别,扩展通过新的存储类关键字可见:__thread。例如:

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

__thread 说明符可以单独使用,与externstatic 说明符一起使用,但不能与其他存储类说明符一起使用。当与externstatic 一起使用时,__thread 必须紧跟在其他存储类说明符之后。

__thread 说明符可应用于类的任何全局、文件范围的静态、函数范围的静态或静态数据成员。它可能不适用于块范围的自动或非静态数据成员。

所以,您看到的是 GCC 特定的线程本地存储表示法,正如我所指出的以及 GCC 手册中的说明,存储类信息应该在声明中首先出现(并且 GCC 明确表示 __threadstaticextern 之后)。

另见Common variable attributes

【讨论】:

    【解决方案2】:

    重读你引用的文字(强调我的):

    • __thread 关键字必须紧跟在 static 或 extern 关键字之后,如果在变量声明中指定了其中任何一个

    因此,如果两者都没有指定,则此条款不适用。

    至于“为什么”,如果声明是真的(我怀疑它大致正确,但表述不准确),这只是“GNU C”的语法问题,__thread 来自哪里。您可以在 C11 标准中查找等效 _Thread_local 的语法要求。

    【讨论】:

      猜你喜欢
      • 2015-05-07
      • 1970-01-01
      • 1970-01-01
      • 2015-06-30
      • 2015-08-14
      • 2014-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多