C++ c++;吸气剂临界截面

C++ c++;吸气剂临界截面,c++,multithreading,critical-section,C++,Multithreading,Critical Section,我有一个简单的类,它有一个私有成员,可以在多线程环境(多读/多写器)中通过get()和set()访问。如何锁定Get(),因为它只有一个return语句 class MyValue { private: System::CriticalSection lock; int val { 0 }; public: int SetValue(int arg) { lock.Enter(); val = arg; l

我有一个简单的类,它有一个私有成员,可以在多线程环境(多读/多写器)中通过get()和set()访问。如何锁定Get(),因为它只有一个return语句

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()?
    }   
}

考虑在ctor中使用类包装器锁定,在dtor中使用类包装器解锁。见标准实施:


通过这种方式,您不需要记住在代码中抛出复杂代码或异常时解锁,从而改变正常执行。

我不是多线程专家,但我认为以下方法应该有效

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

不要锁任何东西。在您的示例中,如果将成员设置为
std::atomic
整数就足够了


这里你不需要其他东西。事实上,由于Intel架构(强内存排序模型),这个
std::atomic
甚至不可能导致任何性能问题。

这是hauron回答中同步对象的一个演示——我想表明对象构造和销毁开销在优化构建中根本不存在

在下面的代码中,CCsGrabber是一个类似RAII的类,它在构造时进入一个关键部分(由一个CCCritical对象包装),然后在销毁时离开:

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),该对象与一个本地CCsGrabber实例(csg)一起用于
SerialFunc()
,以负责锁定和解锁:

CCritical cs;
DWORD last_tick = 0;

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

int main() {
    SerialFunc();
    std::cout << last_tick << std::endl;
}
使用显式放置的
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)  
                         ...
intmain(){
00401C80推式ebp
00401C81 mov ebp,esp
00401C83和esp,0FFFFF8H
00401C86推0FFFFFFFFh
00401C88推压41B038h
00401C8D mov eax,dword ptr fs:[00000000小时]
00401C93推式eax
00401C94 mov dword ptr fs:[0],esp
00401C9B子esp,0Ch
00401C9E推式esi
00401C9F推式电子数据交换
SerialFunc();
00401CA0推送427B78h
00401CA5呼叫dword ptr ds:[41C00Ch]
00401CAB呼叫dword ptr ds:[41C000h]
00401CB1推送427B78h
00401CB6 mov dword ptr ds:[00427B74h],eax
00401CBB呼叫dword ptr ds:[41C008h]


std::cout但如果线程A正在执行“return ret”,而线程b正在执行int ret=val???@bsobaid如果我正确理解多线程,那么当他执行函数时,每个线程都会有它自己的所有局部变量的副本(
int ret
int)。因此,这不会是一个问题。@bsobaid那么你将有一个脏读(google it:当允许事务从已被另一个正在运行的事务修改但尚未提交的行中读取数据时,会发生脏读(也称为未提交依赖项)实际上,我非常喜欢脏读。I-e线程A读取val as10,当返回ret时,线程B来设置val=20。在这种情况下,线程A有一个陈旧的值。我同意。@bsobaid@José@hauron,但每个线程都有自己的堆栈。每个线程都有自己的
int-ret;
,所以“脏读”这是不可能的。或者我理解错误了吗?在我的例子中,100个线程在一秒钟内调用了数千次。构造和销毁这么多对象将是一项开销?应用程序对性能非常敏感。@b如果是这样,那么我恐怕这将是一项开销(上面的锁定/解锁机制也是如此)您需要的是一个共享的唯一互斥组合。一个写入器(SETER)应该尝试锁定互斥锁。只有当没有读卡器(GETER)持有锁时,才会成功。阅读器不需要独占访问,并且可以保持互斥锁的互斥。@ BSUAID考虑这个链接:(有关实现,请参阅boost的升级锁:一个可以“升级”到唯一访问的互斥锁)我使用VS2013。当互斥模式进入内核模式时,它不会很昂贵,而临界区仍然保持在用户模式中,直到竞争。@标准BC++中没有BPASID,没有进程间,只有线程间。要进行进程间同步,必须使用操作系统设施。如果调用它,经常使用原子变量。如果C++是N。ot可用windows上有系统功能,比如Interlock系列。真的吗?哇。我会查一下。这个对象的上下文重要吗?因为它作为一个“值”存在在无序映射的本地实现上。因此,即使一次有多个读卡器和多个写卡器线程访问它,它也会工作?@bsobaid,不管对象位于何处。您正在使成员原子化,即每次读取它时,都应该正确地读取它(以便看到最新的更新),并且当您写入时,必须提交写入。它还禁止重新排序。所以您很酷;)我使用VS2013。我需要包含任何内容吗?intellisense在std::.之后不显示原子。它显示std::\u atomic\u integral\t,但我相信这是不同的。GetVal()和SetVal()的返回和arg类型还应该改为std::atomic_int,或者我认为可以保留为int?不,事实上,你不能将它们设置为原子。它们将保留为int。CRITSEC_对象定义在哪里?很抱歉——CRITSEC_对象只是我在粘贴代码时遗漏的CRITICAL_部分的typedef。我进行了编辑,将其替换为CRITICAL_部分.np.与EnterCriticalSection()、LeaveCriticalSection()相比,您的CriticalSection和System::CriticalSection有什么区别我相信C风格和系统中的::批评节是C++ + @ bsAuto cDebug是一个C++类,它管理Win32临界段的生命周期,并提供与API调用对应的成员函数,这些函数调用在拥有的临界段对象上。它为Win32 API的C类接口提供了一个C++包装器。屁股
void SerialFunc() {
    cs.Enter();
    last_tick = GetTickCount();
    cs.Leave();
}
    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)  
                         ...