Estoy confundido con la descripción de thread_local en C++ 11. Según entiendo, cada hilo tiene una copia única de variables locales en una función. Se puede acceder a las variables globales/estáticas por todos los hilos (posiblemente acceso sincronizado usando bloqueos). ¿Y las variables thread_local son visibles para todos los hilos, pero solo pueden ser modificadas por el hilo para el que están definidas? ¿Es correcto?¿Qué significa thread_local en C++ 11?
Respuesta
Thread-local storage duration es un término utilizado para referirse a datos que son aparentemente globales o de almacenamiento estático (desde el punto de vista de las funciones que lo utilizan) pero en realidad hay una copia por hilo.
Se agrega a la corriente automática (existe durante un bloque/función), estática (existe para la duración del programa) y dinámica (existe en el montón entre la asignación y desasignación).
Algo que es thread-local se crea al crear el hilo y se elimina cuando se detiene el hilo.
A continuación se incluyen algunos ejemplos.
Piense en un generador de números aleatorios donde la semilla se debe mantener por subproceso. Usar una semilla local de hilo significa que cada hilo obtiene su propia secuencia de números aleatorios, independientemente de otros hilos.
Si su semilla era una variable local dentro de la función aleatoria, se inicializaría cada vez que la llamara, y le daría el mismo número cada vez. Si fuera global, los hilos interferirían con las secuencias de los demás.
Otro ejemplo es algo así como strtok
donde el estado de tokenización se almacena en un subproceso específico. De esta forma, un solo hilo puede estar seguro de que otros hilos no arruinarán sus esfuerzos de tokenización, mientras que aún puede mantener el estado en múltiples llamadas al strtok
; esto básicamente hace que strtok_r
(la versión de seguridad de subprocesos) sea redundante.
Ambos ejemplos permiten que la variable local de subproceso exista dentro de la función que lo utiliza. En el código prehebra, simplemente sería una variable de duración de almacenamiento estática dentro de la función. Para los hilos, eso se modifica para enhebrar la duración de almacenamiento local.
Otro ejemplo sería algo así como errno
. No desea que los hilos separados modifiquen errno
después de que una de sus llamadas falla pero antes de que pueda verificar la variable, y sin embargo, solo quiere una copia por hilo.
This site tiene una descripción razonable de los diferentes especificadores de duración de almacenamiento.
Thread-local storage está en cada aspecto como almacenamiento estático (= global), solo que cada hilo tiene una copia separada del objeto. La vida útil del objeto comienza en el inicio del hilo (para variables globales) o en la inicialización inicial (estática del bloque local) y finaliza cuando el hilo finaliza (es decir, cuando se llama al join()
).
En consecuencia, sólo variables que también podría ser declarado static
pueden declararse como thread_local
, es decir, las variables globales (más concretamente: variables "en alcance espacio de nombres"), miembros de la clase estáticos y variables de bloque estático (en cuyo caso static
es implícito).
A modo de ejemplo, supongamos que tiene un grupo de subprocesos y quiere saber qué tan bien se estaba equilibrado su carga de trabajo:
thread_local Counter c;
void do_work()
{
c.increment();
// ...
}
int main()
{
std::thread t(do_work); // your thread-pool would go here
t.join();
}
Esto sería imprimir las estadísticas de uso de hilo, por ejemplo, con una aplicación como esta:
struct Counter
{
unsigned int c = 0;
void increment() { ++c; }
~Counter()
{
std::cout << "Thread #" << std::this_thread::id() << " was called "
<< c << " times" << std::endl;
}
};
Cuando se declara una variable thread_local
continuación, cada hilo tiene su propia copia. Cuando lo menciona por su nombre, se utiliza la copia asociada con el hilo actual. p.ej.
thread_local int i=0;
void f(int newval){
i=newval;
}
void g(){
std::cout<<i;
}
void threadfunc(int id){
f(id);
++i;
g();
}
int main(){
i=9;
std::thread t1(threadfunc,1);
std::thread t2(threadfunc,2);
std::thread t3(threadfunc,3);
t1.join();
t2.join();
t3.join();
std::cout<<i<<std::endl;
}
Este código es la salida "2349", "3249", "4239", "4329", "2439" o "3429", pero nunca otra cosa. Cada hilo tiene su propia copia de i
, que se asigna a, se incrementa y luego se imprime. El subproceso que ejecuta main
también tiene su propia copia, que se asigna al principio y luego se deja sin cambios. Estas copias son completamente independientes, y cada una tiene una dirección diferente.
es sólo el nombre deque es especial en ese sentido --- si se toma la dirección de una variable thread_local
a continuación, sólo tiene un puntero normal a un objeto normal, lo que puede pasar libremente entre las roscas. p.ej.
thread_local int i=0;
void thread_func(int*p){
*p=42;
}
int main(){
i=9;
std::thread t(thread_func,&i);
t.join();
std::cout<<i<<std::endl;
}
Desde la dirección de i
se pasa a la función del hilo, a continuación, la copia de i
perteneciente al hilo principal se puede asignar a pesar de que es thread_local
. Este programa dará como resultado "42". Si hace esto, debe tener cuidado de que no se acceda al *p
después de que el hilo al que pertenece haya salido; de lo contrario, obtendrá un puntero colgante y un comportamiento indefinido como cualquier otro caso donde se destruye el objeto apuntado.
thread_local
Las variables se inicializan "antes del primer uso", por lo tanto, si nunca son tocadas por un hilo determinado, no necesariamente se inicializan. Esto es para permitir que los compiladores eviten construir cada variable thread_local
en el programa para un subproceso que sea completamente autónomo y no toque ninguno de ellos. p.ej.
struct my_class{
my_class(){
std::cout<<"hello";
}
~my_class(){
std::cout<<"goodbye";
}
};
void f(){
thread_local my_class;
}
void do_nothing(){}
int main(){
std::thread t1(do_nothing);
t1.join();
}
En este programa hay 2 hilos: el hilo principal y el hilo creado manualmente. Ninguno de los hilos llama a f
, por lo que el objeto thread_local
nunca se utiliza. Por lo tanto, no se especifica si el compilador construirá 0, 1 o 2 instancias de my_class
, y la salida puede ser "", "hellohellogoodbyegoodbye" o "hellogoodbye".
Creo que es importante tener en cuenta que la copia local de la variable es una copia recién inicializada de la variable. Es decir, si agrega una llamada 'g()' al comienzo de 'threadFunc', entonces la salida será' 0304029' o alguna otra permutación de los pares '02',' 03', y '04'. Es decir, aunque 9 se asigna a 'i' antes de que se creen los hilos, los hilos obtienen una copia recién construida de' i' donde 'i = 0'. Si a 'i' se le asigna' thread_local int i = random_integer() ', cada thread obtiene un nuevo entero aleatorio. –
- 1. C++ 11 thread_local palabra clave compatible en visual studio 11
- 2. ¿Qué significa esta construcción C++/C++ 11?
- 3. ¿Qué significa '??' significa en C#?
- 4. ¿Qué significa ~ en C++?
- 5. ¿Qué significa ** en C
- 6. ¿Qué significa "&" en C++?
- 7. ¿Qué significa :: en C++?
- 8. ¿Qué significa [,] en C#?
- 9. ¿Qué significa :: :: en C++?
- 10. ¿Qué significa^= en c/C++?
- 11. ¿Qué significa llamar a bitmap.recycle() en la API 11+?
- 12. ¿Qué significa "para (;;)" en C#?
- 13. ¿Qué significa '?' hacer en C++?
- 14. ¿Qué significa "var" en C#?
- 15. ¿Qué significa "() =>" en C#?
- 16. ¿Qué significa -> en C++?
- 17. ¿Qué significa "clase:" en C++?
- 18. ¿Qué hace? en C significa?
- 19. ¿Qué significa/*! */Mean en C#?
- 20. ¿Qué significa "= 0" en C++?
- 21. ¿Qué significa #pragma en C?
- 22. ¿Qué significa (vacío **) en C?
- 23. ¿Qué significa EPS en C?
- 24. ¿Qué significa() => en C#?
- 25. ¿Qué significa Lejos en c?
- 26. ¿Qué significa cmd/C?
- 27. qué significa global :: C#
- 28. ¿Qué significa \ x en c/C++?
- 29. Delegado en C++ 11
- 30. especialización parcial en C++ 11
+1 para la sugerencia 'strtok'! –
Usar hilo local no resuelve los problemas con 'strtok'. 'strtok' está roto incluso en un único entorno con hebras. –
Disculpa, déjame reformular eso. No presenta ningún _nuevo problema con strtok :-) – paxdiablo