internalcontextbase.cpp
来自「C语言库函数的原型,有用的拿去」· C++ 代码 · 共 1,283 行 · 第 1/5 页
CPP
1,283 行
/// </summary>
void InternalContextBase::RejoinScheduler()
{
EnterCriticalRegion();
ASSERT(SchedulerBase::FastCurrentContext() == this);
ASSERT(m_pVirtualProcessor == NULL);
ASSERT(m_pGroup != NULL);
ASSERT(IsBlocked());
// Switch out - this will take care of putting this context on a runnables queue and waking up a virtual processor
// if one is available.
SwitchOut(Nesting);
ExitCriticalRegion();
}
/// <summary>
/// Wait for work algorithm:
///
/// We search numSearches times through the loop looking for work and then we allow other threads to claim this virtual processor
/// while we deactivate the virtual processor root, asking to be woken up when all processor write buffers are flushed
/// (signified by the m_fIsVisibleVirtualProcessor flag). After that we do *one* more full search for work.
///
/// Scenario #1: If the final sweep (after the flush) does not yield any work, we set the virtual processor to Idle
/// deactivate the virtual processor root. We will be woken up when work comes in, when we are canceled
/// due to the scheduler shutting down, or when the virtual processor we're running on was marked for retirement.
/// If we are not canceled, we simply reset and start over. If we are, we cleanup and exit the dispatch loop.
///
/// Scenario #2: If the final sweep does find work and this virtual processor is still available, we claim it and
/// do work.
///
/// Scenario #3: If the final sweep does find work and this virtual processor is not available, then either someone has activated
/// the underlying root while adding more work (via StartupIdleVirtualProcessor), which takes it from available list
/// (pVProc->IsAvailable()), or the virtual processor we're running on was retired due to core migration (via RemoveVirtualProcessors).
/// In this we execute a Deactivate to consume the activation. Note that even if we were marked for retirement, we will execute
/// the chore we just picked up, which will delay retirement a bit.
/// </summary>
void InternalContextBase::WaitForWork()
{
const unsigned int numSearches = 256;
++m_searchCount;
CMTRACE(MTRACE_EVT_SFW_NEXTLOOP, this, m_pVirtualProcessor, m_searchCount);
if (m_searchCount < numSearches)
{
// Yield thread helps perf for oversubscribed vprocs
m_pThreadProxy->YieldToSystem();
CORE_ASSERT(!m_fIsVisibleVirtualProcessor);
// Do another search for the work within the loop.
}
else if (m_searchCount == numSearches)
{
// At this point virtual processor has to be un-available for everyone but this
// internal context.
CORE_ASSERT(!m_pVirtualProcessor->IsAvailable());
// At this point, we've made the virtual processor 'visible' to the rest of the scheduler. This means that anyone
// adding work to the scheduler is able to grab this virtual processor and assume that it will find the work that
// was added.
m_fIsVisibleVirtualProcessor = true;
// Make this virtual processor available and force all tasks to be visible. The idea is that any work queued *before*
// the point at which we made the virtual processor available is visible after the API call, and we should be able to
// make one single pass through the scheduler to find any such work. Work queued *after* the point at which we made
// the virtual processor available should be able to wake up the virtual processor.
m_pVirtualProcessor->MakeAvailable();
m_pVirtualProcessor->EnsureAllTasksVisible(this);
// If we find work during our final search, we will reclaim the virtual processor and reset the search count.
// Context could not have been canceled since this vproc is not 'idle'.
CORE_ASSERT(m_fCanceled == 0);
}
else
{
CORE_ASSERT(m_searchCount == numSearches + 1);
CORE_ASSERT(m_fIsVisibleVirtualProcessor);
if (m_fWorkSkipped)
{
m_searchCount--;
//
// Account for the cases where we fail to check some of the work-stealing queues due to task pool cancellation in progress.
// If we skip work and deactivate the vproc, the work left in the queue could never be picked up. An example would be a
// chore that blocks on a win32 event that is to be signaled by a queued chore which essentially requires stealing. If the
// only other vproc goes idle incorrectly (because it wasn't able to steal from this queue), then the application would hang.
//
//
// Go back and search again. Hopefully the owning context of the wsq that was skipped has finished canceling.
//
m_pThreadProxy->YieldToSystem();
}
else
{
// Notify the scheduler that this virtual processor is idle, right before going into a sleep state.
m_pScheduler->VirtualProcessorIdle(true);
// Note that the previous call to VirtualProcessorIdle could well be the one that takes the scheduler into PhaseTwoShutdown,
// if this happens to be the last active vproc in the scheduler to go idle, AND no external references to the scheduler exist,
// AND no work remains. In this case we expect that this context is canceled.
CORE_ASSERT( !m_fCanceled || m_pScheduler->InFinalizationSweep() || m_pScheduler->HasCompletedShutdown());
// Deactivate the virtual processor for real this time. We will return out of deactivate for one of the following reasons:
// 1] Someone adds work and wakes us up.
// 2] The scheduler hass shutdown and this context was canceled (and woken up)
// 3] The RM is in the process of removing virtual processors dut do core migration, and marks the underlying vproc for retirement.
// Even if we were canceled during the call to virtual processor idle, we must call deactivate to consume the
// signal sent to the thread proxy (via Activate, when the virtual processor was canceled).
#if defined(_DEBUG)
bool fRMAwaken = false;
#endif // _DEBUG
CORE_ASSERT(!IsBlocked());
while ( !m_pVirtualProcessor->Deactivate(this))
{
//
// The resource manager has woken us up because of a completion notification from the completion list. It's entirely possible
// that another thread was running, pulled it, and went idle. Since we are in idle the VirtualProcessorIdle call above, that other virtual
// processor could have triggered finalization and we could be *IN* phase two shutdown right now. Racing with phase two shutdown
// and VirtualProcessorIdle() / ClaimExclusiveOwnership() is *NOT* healthy for correct finalization. Instead, we simply pretend that this
// virtual processor is still idle and simply *borrow* the *thread* to move completion list items. The movement will translate into
// AddToRunnables calls which will activate virtual processors if there was something blocked. If that was the case, we couldn't be in phase
// two finalization.
//
#if defined(_DEBUG)
fRMAwaken = true;
#endif // _DEBUG
RMAwaken();
//
// At this point, if things have moved from the completion list to runnables, the virtual processor might have been activated. The RM will handle
// the activate/deactivate race and the looping around will swallow the activate and let us continue running in *PROPER FORM*.
//
}
CMTRACE(MTRACE_EVT_WOKEAFTERDEACTIVATE, this, m_pVirtualProcessor, NULL);
#if defined(_DEBUG)
if (fRMAwaken)
{
SetDebugBits(CTX_DEBUGBIT_ACTIVATEDAFTERRMAWAKEN);
}
#endif // _DEBUG
CORE_ASSERT( !m_fCanceled || m_pScheduler->InFinalizationSweep() || m_pScheduler->HasCompletedShutdown());
// We were woken up for one of the 3 reasons above. It is important to tell the scheduler we are not idle before
// proceeding. If we were woken up due to the addition of work we need to ensure that the work is visible to the scheduler
// until after we've reported that we are !IDLE. The scheduler makes one pass looking for work and blocked contexts
// once all virtual processors have reported that they are idle, and if it doesn't find any it will finalize the scheduler.
// Therefore, we must register as ACTIVE, via VirtualProcessorIdle(false), *before* removing any work from the scheduler queues.
// In addition, the call allows us to synchronize with finalization. If we were woken up for any reason, and finalization is in
// progress at the same time, we need to ensure the context does not continue to execute its dispatch loop while the scheduler
// is finalizing.
m_pScheduler->VirtualProcessorIdle(false);
// It is possible that the previous call to VirtualProcessorIdle(false) was suspended on the shutdown gate if
// the scheduler is in the middle of shutdown, in which case the context could be canceled.
// It is ok for the context to canceled right after its virtual processor was marked for retirement. We simply exit
// the dispatch loop, and the virtual processor root in question is destroyed when the scheduler invokes
// ISchedulerProxy::Shutdown, instead of IVirtualProcessorRoot::Remove, which is the normal path for
// retired virtual processors.
CORE_ASSERT(!m_fCanceled || m_pScheduler->InFinalizationSweep() || m_pScheduler->HasCompletedShutdown());
CORE_ASSERT(!m_pVirtualProcessor->IsAvailable());
CORE_ASSERT(m_pVirtualProcessor->GetExecutingContext() == this);
m_fIsVisibleVirtualProcessor = false;
m_searchCount = 0;
}
}
}
/// <summary>
/// This function is called to execute the associated chore if one is available. The chore can be a stolen unrealized
/// chore or realized chore.
/// </summary>
/// <returns>
/// Returns true if an associated chore was executed, false otherwise.
/// </returns>
bool InternalContextBase::ExecutedAssociatedChore()
{
if (m_pAssociatedChore != NULL)
{
#if defined(_DEBUG)
m_workStartTimeStamp = __rdtsc();
m_prepareCount = 0;
#endif // _DEBUG
ExitCriticalRegion();
if (m_fAssociatedChoreStolen)
{
static_cast<_UnrealizedChore*> (m_pAssociatedChore)->_Invoke();
}
else
{
RealizedChore * pRealizedChore = static_cast<RealizedChore*> (m_pAssociatedChore);
pRealizedChore->Invoke();
m_pScheduler->ReleaseRealizedChore(pRealizedChore);
}
EnterCriticalRegion();
// Clean up chore
m_pAssociatedChore = NULL;
ReleaseWorkQueue();
return true;
}
return false;
}
/// <summary>
/// Performs the necessary cleanup for a canceled context in its dispatch routine.
/// <summary>
void InternalContextBase::CleanupDispatchedContextOnCancel()
{
ASSERT(SchedulerBase::FastCurrentContext() == this);
ASSERT(m_fCanceled);
#if defined(_DEBUG)
//
// At this point, we're shutting down this vproc/thread and we do not want to perform lock/heap validations that are no longer
// true.
//
SetShutdownValidations();
#endif // _DEBUG
// This indicates that the vproc is going away. From now until the end of time, this vproc is in a hyper-critical region.
// We are no longer responsible for scheduling anything, so this is "perfectly" safe -- we cannot deadlock
// between ourselves and some arbitrary piece of code we are responsible for scheduling.
EnterHyperCriticalRegion();
// The cleanup call *must* occur before the context releases its reference count on the scheduler.
// Part of cleanup involves releasing a reference on the context's schedule group, and in some cases
// we may need to spin until is safe to do so, keeping both the group and the scheduler alive.
Cleanup();
// NOTE: This call to DecrementInternalContextCount may well be the call that deletes the scheduler. The *this* pointer
// should not be touched after this point!
m_pScheduler->DecrementInternalContextCount();
}
/// <summary>
/// Called in the dispatch loop to check if the virtual processor the context is running on is marked for retirement,
/// and retires the virtual processor if it is.
/// <summary>
/// <summmary>
/// Returns true if the virtual processor was retired, false otherwise.
/// </summary>
bool InternalContextBase::IsVirtualProcessorRetired()
{
ASSERT(SchedulerBase::Fa
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?