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提供的临界区代码,发现也有原子操作,进入内核态睡眠。在操作系统提供的功能下,两种方法是同级的,来自于不同产品的包装而已。