Проблема
Автоматический или локальная переменная потока, сбегающая из потока C11, происходит, когда автоматическая локальная переменная или локальная переменная потока передаются адресом от одного потока C11 до другого, не гарантируя, что переменная остается в живых через длительность последнего потока.
Риск
Автоматическая локальная переменная или локальная переменная потока выделяются на стеке в начале потока, и его время жизни расширяет до конца потока. Переменная, как гарантируют, не будет жива, когда различный поток получит доступ к ней.
Например, рассмотрите функцию запуска потока C11 с этими линиями:
int start_thread(thrd_t *tid) {
int aVar = 0;
if(thrd_success != thrd_create(tid, start_thread_child, &aVar) {
...
}
}
thrd_create
функция создает дочерний поток с функцией запуска start_thread_child
и передает адрес автоматической переменной aVar
к этой функции. Когда этот дочерний поток доступы aVar
, родительский поток может завершить выполнение и aVar
больше не находится на стеке. Доступ может привести к чтению непредсказуемых значений.
Фиксация
Когда вы передаете переменную от одного потока до другого, убедитесь, что переменное время жизни совпадает или превышает время жизни обоих потоков. Можно достигнуть этой синхронизации одним из этих способов:
Объявите переменную static
так, чтобы это не выходило из стека, когда текущий поток завершает выполнение.
Динамически выделите устройство хранения данных для переменной так, чтобы это было выделено на куче вместо стека и должно было быть явным образом освобождено. Убедитесь, что освобождение происходит после обоих потоков полное выполнение.
Эти решения требуют, чтобы вы создали переменную в нелокальной памяти. Вместо этого можно использовать другие решения, такие как shared
ключевое слово, доступное с интерфейсом поточной обработки OpenMP, который позволяет вам безопасно совместно использовать локальные переменные через потоки.
Пример – поток выхода автоматической или локальной переменной потока
#include <threads.h>
#include <stdio.h>
int create_child_thread(void *childVal) {
int *res = (int *)childVal;
printf("Result: %d\n", *res);
return 0;
}
void create_parent_thread(thrd_t *tid, int *parentPtr) {
if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
/* Handle error */
}
}
int main(void) {
thrd_t tid;
int parentVal = 1;
create_parent_thread(&tid, &parentVal);
if (thrd_success != thrd_join(tid, NULL)) {
/* Handle error */
}
return 0;
}
В этом примере, значение parentVal
локально для родительского потока, который запускается в main
и продолжается в функциональный create_parent_thread
. Однако в теле create_parent_thread
, адрес этой локальной переменной передается дочернему потоку (поток со стандартной программой запуска create_child_thread
). Родительский поток может завершить выполнение и переменную parentVal
может выйти из осциллографа, когда дочерний поток получает доступ к этой переменной.
Та же проблема появляется, если переменная объявляется как локальная для потока, например, с ключевым словом C11 _Thread_local
(или thread_local
):
_Thread_local int parentVal = 1;
Коррекция – использует статические переменные
Одна возможная коррекция должна объявить переменную parentVal
как static
так, чтобы переменная была на стеке на целое время программы.
#include <threads.h>
#include <stdio.h>
int create_child_thread(void *childVal) {
int *res = (int *)childVal;
printf("Result: %d\n", *res);
return 0;
}
void create_parent_thread(thrd_t *tid, int *parentPtr) {
if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
/* Handle error */
}
}
int main(void) {
thrd_t tid;
static int parentVal = 1;
create_parent_thread(&tid, &parentVal);
if (thrd_success != thrd_join(tid, NULL)) {
/* Handle error */
}
return 0;
}
Коррекция – использует динамическое выделение памяти
Одна возможная коррекция должна динамически выделить устройство хранения данных для переменных, которые будут совместно использованы через потоки и явным образом освободить устройство хранения данных после того, как переменная больше не будет требоваться.
#include <threads.h>
#include <stdio.h>
int create_child_thread(void *childVal) {
int *res = (int *)childVal;
printf("Result: %d\n", *res);
return 0;
}
void create_parent_thread(thrd_t *tid, int *parentPtr) {
if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
/* Handle error */
}
}
int main(void) {
thrd_t tid;
int parentPtr = (int*) malloc(sizeof(int));
if(!parentPtr) {
create_parent_thread(&tid, &parentVal);
if (thrd_success != thrd_join(tid, NULL)) {
/* Handle error */
}
free(parentPtr);
}
return 0;
}