Программа ниже создает десять потоков, используя системный вызов Linux clone()
.Статическая переменная tls
имеет атрибут C11 thread_local
.Потоки выполняют функцию child_func
, которая просто увеличивает переменную tls
и сохраняет увеличенное значение в месте, указанном аргументом arg
.Приращенные значения сохраняются, по одному для каждого потока, в массиве tlsvals
.Функция main
ожидает окончания потоков и затем печатает значения десяти экземпляров переменной tls
.(Я пропустил проверку ошибок и освобождение выделенных стеков в этой версии кода, чтобы сохранить пример кратким.)
#define _GNU_SOURCE
#include <sched.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h> /* For thread_local. */
#define NTHREADS 10
static thread_local int tls = 0;
static int child_func(void *arg)
{
tls++;
*((int *)arg) = tls;
return 0;
}
int main(int argc, char** argv)
{
int tlsvals[NTHREADS];
for (int n = 0; n < NTHREADS; n++) {
const int STACK_SIZE = 65536;
if (clone(child_func, malloc(STACK_SIZE) + STACK_SIZE, CLONE_VM | SIGCHLD, tlsvals+n) == -1) {
perror("clone");
exit(1);
}
}
int ret, status;
while ((ret = wait(&status)) != -1)
;
for (int *p = tlsvals; p < tlsvals + NTHREADS; p++)
printf("The value of tls is %d\n", *p);
return 0;
}
Поскольку переменная tls
помечена thread_local
, я ожидалпрограмма для печати десяти строк The value of tls is 1
, поскольку каждый поток увеличивает переменную один раз от своего начального нулевого значения.Вместо этого я получаю следующий вывод:
The value of tls is 1
The value of tls is 3
The value of tls is 2
The value of tls is 4
The value of tls is 5
The value of tls is 6
The value of tls is 7
The value of tls is 8
The value of tls is 9
The value of tls is 10
Так что, похоже, переменная tls
вовсе не является локальной для потока, а используется всеми потоками, и каждый поток увеличивает одну и ту же переменную.
Код был скомпилирован с GCC версии 9.1.0 на x86_64 с использованием следующей командной строки:
gcc -O2 -Wall -std=c11 tfoo.c -o tfooc
Я также попытался использовать специфический для GCC атрибут __thread
вместо thread_local
, с тем жерезультат.
Глядя на сборку, созданную GCC, я вижу, что переменная доступна через регистр %fs
, как и должно быть:
child_func:
.LFB24:
.cfi_startproc
movl %fs:tls@tpoff, %eax
addl $1, %eax
movl %eax, %fs:tls@tpoff
movl %eax, (%rdi)
xorl %eax, %eax
ret
.cfi_endproc
Что я делаю неправильно, или есть ошибка в реализации thread_local
в Gcc?