Будет следующая часть работы кода как ожидалось в многопоточном сценарии?
int getUniqueID()
{
static int ID=0;
return ++ID;
}
Не необходимо, что идентификаторы, чтобы быть непрерывным - даже если это пропускает значение, это прекрасно. Можно сказать, что, когда эта функция возвращается, возвращенное значение будет уникально через все потоки?
Примечание. Слово почти используется, потому что глобальная переменная будет инициализирована при запуске процесса (т.е. ее конструктор будет вызываться перед вводом main
), тогда как статическая переменная внутри функции будет инициализирована при первом выполнении оператора.
Ваш вопрос неверен с самого начала:
Генератор идентификаторов с локальной статической переменной - потокобезопасный?
В C / C ++ статическая переменная внутри функции или внутри объявления класса / структуры ведет себя ( почти) как глобальная переменная, а не локальная, основанная на стеке.
Следующий код:
int getUniqueID()
{
static int ID=0;
return ++ID;
}
Будет (почти) похож на псевдокод:
private_to_the_next_function int ID = 0 ;
int getUniqueID()
{
return ++ID;
}
с псевдоключевым словом private_to_the_next_function
, делающим переменную невидимой для всех других функций, кроме getUniqueId ...
Здесь static
только скрывает переменную, что делает невозможным доступ к ней из других функций ...
Но даже скрытый, идентификатор переменной остается глобальным: если getUniqueId вызывается несколькими потоками, ID будет таким же потокобезопасным, как и другие глобальные переменные, то есть совсем не поточно-ориентированным .
Прочитав комментарии, я почувствовал, что не достаточно ясно дал свой ответ. Я использую не глобальные / локальные понятия для обозначения их области видимости, а для обозначения их жизненного цикла:
Глобальный объект будет существовать, пока выполняется процесс, а локальный объект, который размещен в стеке, начнет свою жизнь, когда входит в область действия / функцию и перестанет существовать в момент выхода из области действия / функции. Это означает, что глобальное значение сохранит свое значение, а локальное - нет. Это также означает, что глобальный будет совместно использоваться потоками, а локальный - нет.
Добавьте к нему ключевое слово static
, которое имеет разное значение в зависимости от контекста (вот почему использование static
для глобальных переменных и функций в C ++ не рекомендуется в пользу анонимного пространства имен, но я игнорирую).
При квалификации локальной переменной эта локальная переменная перестает вести себя как локальная. Он становится глобальным, скрытым внутри функции. Таким образом, он ведет себя так, как если бы значение локальной переменной было волшебным образом запомнено между вызовами функции, но в этом нет никакого волшебства: переменная является глобальной и останется «живой» до конца программы.
Вы можете «увидеть» это, зарегистрировав создание и уничтожение объекта, объявленного статическим внутри функции. Конструкция произойдет, когда будет выполнен оператор объявления, и уничтожение произойдет в конце процесса:
bool isObjectToBeConstructed = false ;
int iteration = 0 ;
struct MyObject
{
MyObject() { std::cout << "*** MyObject::MyObject() ***" << std::endl ; }
~MyObject() { std::cout << "*** MyObject::~MyObject() ***" << std::endl ; }
};
void myFunction()
{
std::cout << " myFunction() : begin with iteration " << iteration << std::endl ;
if(iteration < 3)
{
++iteration ;
myFunction() ;
--iteration ;
}
else if(isObjectToBeConstructed)
{
static MyObject myObject ;
}
std::cout << " myFunction() : end with iteration " << iteration << std::endl ;
}
int main(int argc, char* argv[])
{
if(argc > 1)
{
std::cout << "main() : begin WITH static object construction." << std::endl ;
isObjectToBeConstructed = true ;
}
else
{
std::cout << "main() : begin WITHOUT static object construction." << std::endl ;
isObjectToBeConstructed = false ;
}
myFunction() ;
std::cout << "main() : end." << std::endl ;
return 0 ;
}
Если вы запустите исполняемый файл без параметров, выполнение никогда не будет проходить через статический объект объявления, и поэтому он никогда не будет создан или разрушен, как показано в журналах:
main() : begin WITHOUT static object construction.
myFunction() : begin with iteration 0
myFunction() : begin with iteration 1
myFunction() : begin with iteration 2
myFunction() : begin with iteration 3
myFunction() : end with iteration 3
myFunction() : end with iteration 2
myFunction() : end with iteration 1
myFunction() : end with iteration 0
main() : end.
Но если вы запустите его с параметром, то объект будет создан при третьем рекурсивном вызове myFunction и уничтожен только при конец процесса, как видно из журналов:
main() : begin WITH static object construction.
myFunction() : begin with iteration 0
myFunction() : begin with iteration 1
myFunction() : begin with iteration 2
myFunction() : begin with iteration 3
*** MyObject::MyObject() ***
myFunction() : end with iteration 3
myFunction() : end with iteration 2
myFunction() : end with iteration 1
myFunction() : end with iteration 0
main() : end.
*** MyObject::~MyObject() ***
Теперь, если вы играете с тем же кодом, но вызываете myFunction через несколько потоков, у вас будут условия гонки в конструкторе myObject. И если вы вызовете эти методы myObject или используете эти переменные myObject в myFunction, вызываемой несколькими потоками, у вас также будут условия гонки.
Таким образом, статическая локальная переменная myObject - это просто глобальный объект, скрытый внутри функции.
Если вам просто нужно несколько монотонно возрастающих (или очень близких к нему) чисел в N потоках, рассмотрите это (k - это некоторое число, такое что 2 ^ k> N):
int getUniqueIDBase()
{
static int ID=0;
return ++ID;
}
int getUniqueID()
{
return getUniqueIDBase() << k + thread_id;
}
++
не обязательно является атомарным, поэтому нет, он не является потокобезопасным. Однако многие среды выполнения C предоставляют атомарные версии, например __ sync_add_and_fetch ()
для gcc и InterlockedIncrement ()
в Windows.
getUniqueID
имеет как минимум два состояния гонки. При инициализации ID
и при увеличении ID
. Я переписал функцию, чтобы более четко отображать расы данных.
int getUniqueID()
{
static bool initialized = false;
static int ID;
if( !initialized )
{
sleep(1);
initialized = true;
sleep(1);
ID = 1;
}
sleep(1);
int tmp = ID;
sleep(1);
tmp += 1;
sleep(1);
ID = tmp;
sleep(1);
return tmp;
}
Приращение обманчиво, оно выглядит настолько маленьким, что может показаться атомарным. Однако это операция загрузки-изменения-сохранения. Загрузите значение из памяти в регистр ЦП. inc
регистр. Сохраните регистр обратно в память.
Используя новый c ++ 0x, вы можете просто использовать тип std :: atomic
.
int getUniqueID()
{
static std::atomic<int> ID{0};
return ++ID;
}
ПРИМЕЧАНИЕ: технически я солгал. Нулевые инициализированные глобальные переменные (включая статику функций) могут храниться в памяти bss, и их не нужно инициализировать после запуска программы. Однако приращение по-прежнему остается проблемой.
Нет, не будет. Ваш процессор должен будет выполнить следующие шаги для выполнения этого кода:
Если поток Во время этой (неатомарной) последовательности происходит переключение, может произойти следующее:
Итак, оба потока возвращают 2.
Нет, все еще есть потенциал для рас, потому что приращение не обязательно атомарно. Если вы используете атомарную операцию для увеличения идентификатора, это должно сработать.