Как обратиться к потокобезопасности служебных данных, используемых для поддержания статических локальных переменных в C++?

Рассмотрите следующий сценарий. У нас есть функция C++ со статической локальной переменной:

void function()
{
    static int variable = obtain();
    //blahblablah
}

функция должна быть вызвана от нескольких потоков одновременно, таким образом, мы добавляем критический раздел для предотвращения параллельного доступа к локальным помехам:

void functionThreadSafe()
{
    CriticalSectionLockClass lock( criticalSection );
    static int variable = obtain();
    //blahblablah
}

но это будет достаточно? Я подразумеваю, что существует некоторое волшебство, которое делает переменную инициализированной не больше, чем однажды. Таким образом, существуют некоторые служебные данные, сохраняемые временем выполнения, которое указывает, были ли каждые локальные помехи уже инициализированы.

Критическое разделит в вышеупомянутом коде, защищают те служебные данные также? Какая-либо дополнительная защита требуется для этого сценария?

5
задан sharptooth 2 June 2010 в 07:38
поделиться

4 ответа

C++ говорит, что ваша статическая переменная должна быть инициализирована только один раз - однако C++ не имеет дело с потоками (пока).

gcc (по крайней мере, на *nix системах) делает соответствующую магию для безопасной защиты нескольких потоков, инициализирующих такую статическую переменную. Согласно http://social.msdn.microsoft.com/Forums/en/vcgeneral/thread/12f8e2c7-d94d-4904-8c79-a0b0d1088e0b , msvc этого не делает - и в таком случае вам придется блокировать инициализацию самостоятельно.

Защита инициализации с помощью критической секции должна защитить все это - т.е. ваша функция functionThreadSafe() в порядке - (если только get() сама не вызывает functionThreadSafe()

http://blogs.msdn.com/b/oldnewthing/archive/2004/03/08/85901.aspx стоит почитать на этот счет.

Лично я, во избежание сюрпризов, постарался бы переписать это так, чтобы вы могли инициализировать переменную самостоятельно, один раз, до создания каких-либо потоков - например,

static int variable = 0;
void init_variable() //call this once, at the start of main()
{
  variable = obtain();
}

void function() 
{
  //use variable, lock if you write to it
}
5
ответ дан 14 December 2019 в 13:27
поделиться

(Я написал это в другом вопросе, но это также ответ на этот)

Вот мое мнение (если вы действительно не можете инициализировать ее до запуска потоков):

Я видел (и использовал) нечто подобное для защиты статической инициализации, используя boost::once

#include <boost/thread/once.hpp>

boost::once_flag flag;

// get thingy
const Thingy & get()
{
    static Thingy thingy;

    return thingy;
}

// create function
void create()
{
     get();
}

void use()
{
    // Ensure only one thread get to create first before all other
    boost::call_once( &create, flag );

    // get a constructed thingy
    const Thingy & thingy = get(); 

    // use it
    thingy.etc..()          
}

В моем понимании, таким образом все потоки ждут boost::call_once, кроме одного, который создаст статическую переменную. Она будет создана только один раз и больше никогда не будет вызываться. И тогда у вас больше нет блокировки.

1
ответ дан 14 December 2019 в 13:27
поделиться

Чтобы избежать блокировки в любом случае, можно поступить следующим образом:

void functionThreadSafe()
{
    static int *variable = 0;
    if (variable == 0)
    {
       CriticalSectionLockClass lock( criticalSection );
       // Double check for initialization in different thread
       if (variable == 0)
       {
          variable = new int(obtain());
       }
    }
    //blahblablah
}
0
ответ дан 14 December 2019 в 13:27
поделиться

Некоторые боковые уловки, которые вы можете попробовать и которые могут решить вашу основную проблему:

  • вы можете сделать переменную int статической thread-local, если разным потокам на самом деле не нужно делиться значением этой переменной или передавать друг другу данные через нее.
  • Для int на x86 можно использовать атомарное чтение/запись, например, InterlockedCompareExchange() или его эквивалент на вашей платформе. Это позволяет нескольким потокам безопасно обращаться к переменной без блокировок. Однако это работает только для аппаратно-нативных атомарных типов (например, 32-битных и 64-битных слов). Вам также придется придумать, что делать, если два потока захотят записать в переменную одновременно (т.е. один из них обнаружит, что другой уже записал в нее, когда будет выполнять операцию сравнения и замены).
0
ответ дан 14 December 2019 в 13:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: