【问题标题】:Why pthread_self is marked with attribute(const)?为什么 pthread_self 标有属性(常量)?
【发布时间】:2016-08-20 23:51:27
【问题描述】:

在 Glibc 的 pthread.h 中,pthread_self 函数使用 const 属性声明:

extern pthread_t pthread_self (void) __THROW __attribute__ ((__const__));

在 GCC 中 that attribute means:

许多函数除了参数之外不检查任何值,并且除了返回值之外没有任何效果。基本上这只是比下面的纯属性稍微严格的类,因为不允许函数读取全局内存。

我想知道那应该是怎样的?由于它不接受任何参数,因此pthread_self 只允许总是返回相同的值,显然情况并非如此。也就是说,我本来希望pthread_self 读取全局内存,因此最终被标记为pure

许多函数除了返回值之外没有任何影响,它们的返回值仅取决于参数和/或全局变量。这样的函数可以像算术运算符一样进行公共子表达式消除和循环优化。这些函数应该用pure属性声明。

x86-64 上的实现似乎实际上是在读取全局内存:

# define THREAD_SELF \
  ({ struct pthread *__self;                                           \
     asm ("mov %%fs:%c1,%0" : "=r" (__self)                            \
          : "i" (offsetof (struct pthread, header.self)));             \
     __self;})

pthread_t
__pthread_self (void)
{
  return (pthread_t) THREAD_SELF;
}
strong_alias (__pthread_self, pthread_self)

这是一个错误还是我没有看到什么?

【问题讨论】:

  • 它总是会返回相同的值在给定的线程中。如果你调用一个纯函数,改变一个全局变量,然后再次调用它,你可能会得到不同的返回值。如果您之间不更改全局变量并且参数相同,则编译器可以假设返回值相同。这不会发生在像 pthread_self() 这样的 __const__ 函数中,因为返回值不依赖于任何全局变量的值。它会在不同的线程中返回不同的值这一事实与这里发生的事情并不真正相关。
  • 该内存实际上并不是全局的,它位于%fs 段中,这是一个通常专用于线程本地存储的特殊段。所以它实际上是在访问线程本地内存。
  • @PaulGriffiths:IOW 与 pure-ness 和 const-ness 以及多线程有交互。很公平,但这并不能解释如何在不读取任何全局内存的情况下实现pthread_self
  • @peppe:没关系。 pthread_self() 返回调用线程的线程 ID,并且在线程的生命周期内永远不会改变。它是这样定义的。如果它确实发生了变化,那么你的实现就会被破坏,所以所有的赌注都没有了。如果你做了一些奇怪的事情来打破它,那么你就会调用未定义的行为,所以所有的赌注都是一样的。您个人无法创建一个全局变量来更改该值,而不做一些奇怪的事情以使您无权依赖该保证。即使它确实读取了全局内存,它的读取方式也不会产生明显的影响。
  • 好吧,如果您使用一个属性然后违反允许使用该属性的条件,那么它确实很重要。这几乎就是这里的问题......

标签: c gcc pthreads glibc


【解决方案1】:

该属性很可能是在假设 GCC 仅在本地(在函数内)使用它并且永远无法将其用于过程间优化的假设中添加的。今天,一些 Glibc 开发人员正在质疑该属性的正确性,因为强大的过程间优化可能会导致错误编译;引用post by Torvald Riegel to Glibc developers' mailing list

const 属性被指定为断言函数不 检查除论点以外的任何数据。 __errno_location 没有 参数,因此它必须每次返回相同的值。 这适用于单线程程序,但不适用于多线程程序 一。因此,我认为严格来说,它不应该是 const。

我们可以争辩说,这神奇地意味着总是在上下文中 的特定线程。忽略 GCC 本身并没有定义线程 (尤其是像 NPTL 这样的东西,它是关于创建一个概念的 线程),我们仍然可以假设这是可行的,因为在实践中, 编译器及其传递不能跨中使用的函数泄漏知识 一个线程,另一个在另一个线程中使用。

__errno_location()pthread_self() 都标有 __attribute__((const)) 并且不接收任何参数)。

这是一个小例子,可能会被强大的过程间分析错误编译:

#include <pthread.h>
#include <errno.h>
#include <stdlib.h>

static void *errno_pointer;

static void *thr(void *unused)
{
  if (!errno_pointer || errno_pointer == &errno)
    abort();
  return 0;
}

int main()
{
  errno_pointer = &errno;
  pthread_t t;
  pthread_create(&t, 0, thr, 0);
  pthread_join(t, 0);
}

(编译器可以观察到errno_pointer 是静态的,它不会转义翻译单元,并且唯一的存储分配给它的相同“const”值,由__errno_location() 给出,在thr() 中测试)。我在my email asking to improve documentation of pure/const attributes 中使用过这个例子,但不幸的是它并没有得到太多的关注。

【讨论】:

  • 感谢您提供有见地的回答(和链接),这向我证实了 constpure 的文档在多线程环境中绝对不清楚...
【解决方案2】:

我想知道那应该是怎样的?

该属性告诉编译器在给定的上下文中pthread_self 将始终返回相同的值。换句话说,下面的两个循环是完全等价的,并且允许编译器优化对pthread_self 的第二次(以及所有后续)调用:

// loop A
std::map<pthread_t, int> m;
for (int j = 0; j < 1000; ++j)
  m[pthread_self()] += 1;

// loop B
std::map<pthread_t, int> m;
const pthread_t self = pthread_self();
for (int j = 0; j < 1000; ++j)
  m[self] += 1;

x86-64 上的实现似乎实际上是在读取全局内存

不,它没有。它读取线程本地内存。

【讨论】:

  • 我知道这是constpure 属性的最终目的,但同样,这并不能完全解释为什么const 而不是pure。 ...文档对这些语义是否有错误? const char array[] = "Hello, world!"; char f(int i) { return array[i]; }中的f可以标记为constpure吗?文档说pure,这里的每个人都说const
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多