2016-03-10 4 views
3

У меня есть простой класс с одним частным членом, который доступен через get() и set() в многопоточной среде (многопользовательские/многопользовательские). как заблокировать Get(), поскольку он имеет только оператор возврата?C++ criticalsection for getter

class MyValue 
{ 
    private: 
    System::CriticalSection lock; 
    int val { 0 }; 

    public: 
    int SetValue(int arg) 
    { 
     lock.Enter(); 
     val = arg; 
     lock.Leave(); 
    } 

    int GetValue() 
    { 
     lock.Enter(); 
     return val; 
     //Where should I do lock.Leave()? 
    } 
} 
+0

Используйте атомную переменную, если она вызывается очень часто. Если C++ недоступен, в системе есть системные функции, такие как семейство Interlock. – knivil

ответ

3

Не блокируйте ничего. В вашем примере этого достаточно, если вы сделаете свой член целым числом std::atomic.

Вам здесь ничего не нужно. На самом деле, из-за архитектуры Intel (модель памяти с большой памятью) этот std::atomic вряд ли может вызвать проблемы с производительностью.

+0

действительно? Вау. Посмотрим. имеет ли смысл контекст этого объекта? потому что он живет как «ценность» при локальной реализации неупорядоченной карты. поэтому он будет работать, даже если к нему одновременно доступны несколько считывателей и несколько потоков сообщений? – bsobaid

+0

std :: atomic будет работать хорошо, если это простой тип – hauron

+0

@bsobaid, не имеет значения, где находится объект. Вы делаете атома атома, говоря, что каждый раз, когда вы его читаете, он должен быть правильно прочитан (чтобы вы видели последнее обновление), и когда вы пишете, записи должны быть зафиксированы. Он также запрещает переупорядочивание. Итак, вы классные;) – SergeyA

1

Рассмотрите возможность использования блокировки класса класса в ctor и разблокировки в dtor. См. Стандартную реализацию: http://en.cppreference.com/w/cpp/thread/unique_lock

Таким образом, вам не нужно запоминать информацию о разблокировке в случае сложного кода или исключений, которые вы выбрали в своем коде, изменив нормальное выполнение.

+0

в моем случае это называется тысячи раз в секундах на 100 потоков. строительство и уничтожение стольких объектов будут накладными? приложение является супер-чувствительным. – bsobaid

+0

@bsobaid, если это так, то я боюсь, что это будет накладные расходы (вышеупомянутый механизм блокировки/разблокировки). Что вам нужно, это совместная уникальная комбинация мьютексов. Писатель (сеттер) должен попытаться заблокировать мьютексы исключительно. Это будет успешным только в том случае, если читатель (геттер) не удерживает блокировку. Считыватель не нуждается в эксклюзивном доступе и может хранить разделяемую блокировку voer в мьютексе. – hauron

+0

@bsobaid считают эту ссылку: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock (для реализации см. Блокировку обновления boost: блокировка над мьютексом, чем «обновление» до уникального доступа) – hauron

2

Я не специалист по многопоточности, но думаю, что следующее должно работать.

int GetValue() 
{ 
    lock.Enter(); 
    int ret = val; 
    lock.Leave(); 
    return ret; 
} 
+0

, но что, если thread A выполняет «return ret», а thread b выполняет int ret = val ?? – bsobaid

+0

@bsobaid Если я правильно понимаю многопоточность, каждый поток будет иметь свою собственную копию всех локальных переменных ('int ret' int this case), когда он выполняет эту функцию. Таким образом, это не будет проблемой. – HolyBlackCat

+0

@bsobaid, тогда у вас будет грязное чтение (google it: грязное чтение (aka uncommitted dependency) возникает, когда транзакции разрешено читать данные из строки, которая была изменена другой запущенной транзакцией и еще не зафиксирована.) – hauron

2

Это демонстрация объекта синхронизации из ответа хаурона - я хотел показать, что конструкция объекта и разрушение накладных расходов просто не существуют с помощью оптоизолированной сборки.

В приведенном ниже коде, CCsGrabber является RAII-подобный класс, который входит в критическую секцию (обернутую объектом CCritical), когда построенный, а затем выходит из него при разрушении:

class CCsGrabber { 
    class CCritical& m_Cs; 
    CCsGrabber(); 
public: 
    CCsGrabber(CCritical& cs); 
    ~CCsGrabber(); 
}; 

class CCritical { 
    CRITICAL_SECTION cs; 
public: 
    CCritical()  { 
     InitializeCriticalSection(&cs); 
    } 
    ~CCritical()  { DeleteCriticalSection(&cs); } 
    void Enter()  { EnterCriticalSection(&cs); } 
    void Leave()  { LeaveCriticalSection(&cs); } 
    void Lock()  { Enter(); } 
    void Unlock()  { Leave(); } 
}; 

inline CCsGrabber::CCsGrabber(CCritical& cs) : m_Cs(cs) { m_Cs.Enter(); } 
inline CCsGrabber::CCsGrabber(CCritical *pcs) : m_Cs(*pcs) { m_Cs.Enter(); } 
inline CCsGrabber::~CCsGrabber()       { m_Cs.Leave(); } 

Теперь, глобальный объект CCritical создается (CS), который используется в SerialFunc(), наряду с локальным экземпляром CCsGrabber (CSG), чтобы заботиться о блокировки и разблокировки:

CCritical cs; 
DWORD last_tick = 0; 

void SerialFunc() { 
    CCsGrabber csg(cs); 
    last_tick = GetTickCount(); 
} 

int main() { 
    SerialFunc(); 
    std::cout << last_tick << std::endl; 
} 

и ниже в dissasembly из основных() из оптимизированная 32- бит. (Прошу прощения за склеивание в целом вещи - я хотел показать, что я ничего не скрываю:

int main() { 
00401C80 push  ebp 
00401C81 mov   ebp,esp 
00401C83 and   esp,0FFFFFFF8h 
00401C86 push  0FFFFFFFFh 
00401C88 push  41B038h 
00401C8D mov   eax,dword ptr fs:[00000000h] 
00401C93 push  eax 
00401C94 mov   dword ptr fs:[0],esp 
00401C9B sub   esp,0Ch 
00401C9E push  esi 
00401C9F push  edi 
    SerialFunc(); 
00401CA0 push  427B78h       ; pointer to CS object 
00401CA5 call  dword ptr ds:[41C00Ch]   ; [email protected]: 
00401CAB call  dword ptr ds:[41C000h]   ; [email protected]: 
00401CB1 push  427B78h       ; pointer to CS object 
00401CB6 mov   dword ptr ds:[00427B74h],eax  ; return value => last_tick 
00401CBB call  dword ptr ds:[41C008h]   ; [email protected]: 
    std::cout << last_tick << std::endl; 
00401CC1 push  ecx 
00401CC2 call  std::basic_ostream<char,std::char_traits<char> >::operator<< (0401D90h) 
00401CC7 mov   esi,eax 
00401CC9 lea   eax,[esp+0Ch] 
00401CCD push  eax 
00401CCE mov   ecx,dword ptr [esi] 
00401CD0 mov   ecx,dword ptr [ecx+4] 
00401CD3 add   ecx,esi 
00401CD5 call  std::ios_base::getloc (0401BD0h) 
00401CDA push  eax 
00401CDB mov   dword ptr [esp+20h],0 
00401CE3 call  std::use_facet<std::ctype<char> > (0403E40h) 
00401CE8 mov   dword ptr [esp+20h],0FFFFFFFFh 
00401CF0 add   esp,4 
00401CF3 mov   ecx,dword ptr [esp+0Ch] 
00401CF7 mov   edi,eax 
00401CF9 test  ecx,ecx 
00401CFB je   main+8Eh (0401D0Eh) 
00401CFD mov   edx,dword ptr [ecx] 
00401CFF call  dword ptr [edx+8] 
00401D02 test  eax,eax 
00401D04 je   main+8Eh (0401D0Eh) 
00401D06 mov   edx,dword ptr [eax] 
00401D08 mov   ecx,eax 
00401D0A push  1 
00401D0C call  dword ptr [edx] 
00401D0E mov   eax,dword ptr [edi] 
00401D10 mov   ecx,edi 
00401D12 push  0Ah 
00401D14 mov   eax,dword ptr [eax+20h] 
00401D17 call  eax 
00401D19 movzx  eax,al 
00401D1C mov   ecx,esi 
00401D1E push  eax 
00401D1F call  std::basic_ostream<char,std::char_traits<char> >::put (0404220h) 
00401D24 mov   ecx,esi 
00401D26 call  std::basic_ostream<char,std::char_traits<char> >::flush (0402EB0h) 
} 
00401D2B mov   ecx,dword ptr [esp+14h] 
00401D2F xor   eax,eax 
00401D31 pop   edi 
00401D32 mov   dword ptr fs:[0],ecx 
00401D39 pop   esi 
00401D3A mov   esp,ebp 
00401D3C pop   ebp 
00401D3D ret 

Таким образом, мы можем видеть, что SerialFunc() был встраиваются непосредственно в основном, после пролога в начале и перед кодом cout - и нигде не может быть обнаружено создание какого-либо суперпоточного объекта, выделение памяти или что-то еще - он просто выглядит как минимальный код сборки, необходимый для входа в критический раздел, получает количество меток в переменной, а затем оставить критическую секцию

Тогда я изменил SerialFunc() на:.

void SerialFunc() { 
    cs.Enter(); 
    last_tick = GetTickCount(); 
    cs.Leave(); 
} 

С явно размещенными cs.Enter() и cs.Leave(), только для сравнения с версией RAII. Сгенерированный код оказался идентичным:

int main() { 
00401C80 push  ebp 
00401C81 mov   ebp,esp 
00401C83 and   esp,0FFFFFFF8h 
00401C86 push  0FFFFFFFFh 
00401C88 push  41B038h 
00401C8D mov   eax,dword ptr fs:[00000000h] 
00401C93 push  eax 
00401C94 mov   dword ptr fs:[0],esp 
00401C9B sub   esp,0Ch 
00401C9E push  esi 
00401C9F push  edi 
     SerialFunc(); 
00401CA0 push  427B78h 
00401CA5 call  dword ptr ds:[41C00Ch] 
00401CAB call  dword ptr ds:[41C000h] 
00401CB1 push  427B78h 
00401CB6 mov   dword ptr ds:[00427B74h],eax 
00401CBB call  dword ptr ds:[41C008h] 
     std::cout << last_tick << std::endl; 
00401CC1 push  ecx 
00401CC2 call  std::basic_ostream<char,std::char_traits<char> >::operator<< (0401D90h) 
         ... 

На мой взгляд, ответ Сергея является лучшим для данной ситуации - критический раздел для синхронизации чтения и записи из/в 32-битных переменных является чрезмерным. Однако, если что-то приходит, которое требует критического раздела или мьютекса, использование объекта типа RAII для упрощения вашего кода, вероятно, не принесет значительных (или даже любых) накладных расходов на создание объектов.

(Я использовал Visual C++ 2013 для компиляции кода выше)

+0

awesom пост. спасибо – bsobaid

+0

где определено CRITSEC_OBJECT? – bsobaid

+0

Извините, что CRITSEC_OBJECT был всего лишь типом CRITICAL_SECTION, который я пропустил, когда я вставил код здесь. Я сделал редактирование, заменив его на CRITICAL_SECTION. –