2线程同步-C++11中的互斥锁

C11中mutex头文件内容

Mutex

类,基本的互斥锁

recursive_mutex

类, 同一线程可以递归调用的互斥锁

timed_mutex

类,在指定的时间内能返回的锁

recursive_timed_mutex

类,在指定的时间内能返回且同一线程能递归调用的锁

adopt_lock_t

空结构体,用于控制unique_lock,lock_guard的处理策略(假定当前线程已经获得互斥对象的所有权,所以不再请求锁。)

例如:std::unique_lock<std::mutex> lck(mt, std:: adopt_lock);

defer_lock_t

空结构体,用于控制unique_lock 的处理策略(不请求锁)

try_to_lock_t

空结构体,用于控制unique_lock 的处理策略(尝试请求锁,但不阻塞线程,锁不可用时也会立即返回。)

lock_guard

构造函数自动加锁,析构函数自动释放锁。

unique_lock

具备lock_guard的功能外,增加增加了解锁,加锁等功能。等于是更小粒度的资源控制。

swap

交换两个互斥锁

try_lock

尝试同时锁多个对象。

lock

同时锁住多个锁对象。

once_flag

结构体用于保存函数调用记录标志。

call_once

保证函数只被调用一次,用once_flag当作记录看是否已经被调用。


C11中的锁在linux 平台上的实现

就是对pthread_mutex对象的封装,具备了pthread_mutex的所有特性。

C11中的锁在win 平台上的实现

是对临界区的封装吗? 答案是否定的。

通过对锁函数上锁的跟踪发现如下代码:

boolcritical_section::_Acquire_lock(void * _PLockingNode,bool _FHasExternalNode)

{

    LockQueueNode * pNewNode =reinterpret_cast<LockQueueNode*>(_PLockingNode);

    LockQueueNode * pActiveNode =reinterpret_cast<LockQueueNode*>(&_M_activeNode);

    if (pNewNode->m_pContext == pActiveNode->m_pContext)

    {

        throw improper_lock("Lock alreadytaken");

    }

 

    LockQueueNode * pPrevious =reinterpret_cast<LockQueueNode*>(InterlockedExchangePointer(&_M_pTail, pNewNode));

// 通过原子操作对指针进行判断,看是否已经上锁,NULL表示未上锁

    if (pPrevious == NULL)

    {

        _M_pHead = pNewNode;

 

       pNewNode->UpdateQueuePosition(pActiveNode);

        pNewNode->UnblockWithoutContext();

        pNewNode->TryCompensateTimer();

    }

    else

    {

       pNewNode->UpdateQueuePosition(pPrevious);

        pPrevious->m_pNextNode = pNewNode;

      //  在下面的函数中去竞争锁

        pNewNode->Block(pActiveNode->m_ticketState);

 

         if(pNewNode->m_trigger != TriggeredByTimeout)

        {

           pNewNode->UpdateQueuePosition(pActiveNode);

        }

    }

 

   

if (_FHasExternalNode)

    {

        pActiveNode->Copy(pNewNode);

        _M_pHead = pNewNode;

    }

 

   

returnpNewNode->m_trigger != TriggeredByTimeout;

}

我们继续跟踪竞争锁的代码

void Block(unsignedint currentTicketState =0)

        {

            unsignedint numberOfProcessors =Concurrency::GetProcessorCount();

            _CONCRT_ASSERT(numberOfProcessors> 0);


            if (!IsPreviousBlocked())

            {

                unsignedint placeInLine =IsTicketValid() ? ((m_ticketState >> NumberOfBooleanStates) -(currentTicketState >> NumberOfBooleanStates)) : 1;

                _CONCRT_ASSERT(placeInLine >0);

 

               

                if (placeInLine <=numberOfProcessors + TicketThreshold)

                {

                    constunsignedint defaultSpin =_SpinCount::_Value();

                    unsignedint totalSpin =defaultSpin + (defaultSpin * (placeInLine - 1)) / (numberOfProcessors +TicketThreshold);

 

                    _SpinWaitNoYield spinWait;

                   spinWait._SetSpinCount(totalSpin);

 

           // 在这边开始自旋,自旋到一定次数后,跳出这个循环

                    while (IsBlocked() &&spinWait._SpinOnce())

                    {

                    }

                }

            }

            //如果自旋完成后,依然得不到锁,本线程进入这个函数后,陷入内核态开始睡眠

            m_pContext->Block();

        }

睡眠函数如下:

void ExternalContextBase::Block()

    {

        ASSERT(this ==SchedulerBase::FastCurrentContext());

 

        TraceContextEvent(CONCRT_EVENT_BLOCK,TRACE_LEVEL_INFORMATION, m_pScheduler->Id(), m_id);

 

        if(InterlockedIncrement(&m_contextSwitchingFence) == 1)

        {

            WaitForSingleObjectEx(m_hBlock,INFINITE, FALSE);

        }

        else

        {

        }

    }

调用WaitForSingleObjectEx函数而进入内核态。

总结

C11锁的原理就是先自旋,不能成功后,执行WaitForSingleObjectEx函数后进入内核态睡眠。

通过跟踪windos提供的临界区代码,发现也有原子操作,进入内核态睡眠。在操作系统提供的功能下,两种方法是同级的,来自于不同产品的包装而已。

猜你喜欢

转载自blog.csdn.net/pi314/article/details/53174455
今日推荐