示例#1
0
        /// <summary>
        /// Performs a single spin.
        /// </summary>
        /// <remarks>
        /// This is typically called in a loop, and may change in behavior based on the number of times a
        /// <see cref="SpinOnce"/> has been called thus far on this instance.
        /// </remarks>
        public void SpinOnce()
        {
            if (NextSpinWillYield)
            {
                //
                // We must yield.
                //
                // We prefer to call Thread.Yield first, triggering a SwitchToThread. This
                // unfortunately doesn't consider all runnable threads on all OS SKUs. In
                // some cases, it may only consult the runnable threads whose ideal processor
                // is the one currently executing code. Thus we oc----ionally issue a call to
                // Sleep(0), which considers all runnable threads at equal priority. Even this
                // is insufficient since we may be spin waiting for lower priority threads to
                // execute; we therefore must call Sleep(1) once in a while too, which considers
                // all runnable threads, regardless of ideal processor and priority, but may
                // remove the thread from the scheduler's queue for 10+ms, if the system is
                // configured to use the (default) coarse-grained system timer.
                //

#if !FEATURE_PAL && !FEATURE_CORECLR   // PAL doesn't support  eventing, and we don't compile CDS providers for Coreclr
                CdsSyncEtwBCLProvider.Log.SpinWait_NextSpinWillYield();
#endif
                int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count - YIELD_THRESHOLD : m_count);

                if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1))
                {
                    Thread.Sleep(1);
                }
                else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1))
                {
                    Thread.Sleep(0);
                }
                else
                {
#if PFX_LEGACY_3_5
                    Platform.Yield();
#else
                    Thread.Yield();
#endif
                }
            }
            else
            {
                //
                // Otherwise, we will spin.
                //
                // We do this using the CLR's SpinWait API, which is just a busy loop that
                // issues YIELD/PAUSE instructions to ensure multi-threaded CPUs can react
                // intelligently to avoid starving. (These are NOOPs on other CPUs.) We
                // choose a number for the loop iteration count such that each successive
                // call spins for longer, to reduce cache contention.  We cap the total
                // number of spins we are willing to tolerate to reduce delay to the caller,
                // since we expect most callers will eventually block anyway.
                //
                Thread.SpinWait(4 << m_count);
            }

            // Finally, increment our spin counter.
            m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1);
        }
示例#2
0
        /// <summary>
        /// Try acquire the lock with long path, this is usually called after the first path in Enter and
        /// TryEnter failed The reason for short path is to make it inline in the run time which improves the
        /// performance. This method assumed that the parameter are validated in Enter ir TryENter method
        /// </summary>
        /// <param name="millisecondsTimeout">The timeout milliseconds</param>
        /// <param name="lockTaken">The lockTaken param</param>
        private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
        {
            long startTicks = 0;

            if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
            {
                startTicks = DateTime.UtcNow.Ticks;
            }

#if !FEATURE_PAL && !FEATURE_CORECLR   // PAL doesn't support  eventing, and we don't compile CDS providers for Coreclr
            if (CdsSyncEtwBCLProvider.Log.IsEnabled())
            {
                CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner);
            }
#endif

            if (IsThreadOwnerTrackingEnabled)
            {
                // Slow path for enabled thread tracking mode
                ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTicks, ref lockTaken);
                return;
            }

            // then thread tracking is disabled
            // In this case there are three ways to acquire the lock
            // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2
            // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn
            // the late the thread arrives the more it spins and less frequent it check the lock avilability
            // Also the spins count is increaes each iteration
            // If the spins iterations finished and failed to acquire the lock, go to step 3
            // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
            // If the timeout is expired in after step 1, we need to decrement the waiters count before returning

            int observedOwner;

            //***Step 1, take the lock or update the waiters

            // try to acquire the lock directly if possoble or update the waiters count
            SpinWait spinner = new SpinWait();
            while (true)
            {
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
#if !FEATURE_CORECLR
                    Thread.BeginCriticalRegion();
#endif

#if PFX_LEGACY_3_5
                    if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner) == observedOwner)
                    {
                        lockTaken = true;
                        return;
                    }
#else
                    if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
                    {
                        return;
                    }
#endif

#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
#endif
                }
                else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow
                if ((observedOwner & WAITERS_MASK) == MAXIMUM_WAITERS || Interlocked.CompareExchange(ref m_owner, observedOwner + 2, observedOwner) == observedOwner)
                {
                    break;
                }

                spinner.SpinOnce();
            }

            // Check the timeout.
            if (millisecondsTimeout == 0 ||
                (millisecondsTimeout != Timeout.Infinite &&
                 TimeoutExpired(startTicks, millisecondsTimeout)))
            {
                DecrementWaiters();
                return;
            }

            //***Step 2. Spinning
            //lock acquired failed and waiters updated
            int turn           = ((observedOwner + 2) & WAITERS_MASK) / 2;
            int processorCount = PlatformHelper.ProcessorCount;
            if (turn < processorCount)
            {
                int processFactor = 1;
                for (int i = 1; i <= turn * SPINNING_FACTOR; i++)
                {
                    Thread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor);
                    if (processFactor < processorCount)
                    {
                        processFactor++;
                    }
                    observedOwner = m_owner;
                    if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                    {
#if !FEATURE_CORECLR
                        Thread.BeginCriticalRegion();
#endif

                        int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                                       observedOwner | 1                     // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                            : (observedOwner - 2) | 1;                       // otherwise decrement the waiters and set the lock bit
                        Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#if PFX_LEGACY_3_5
                        if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
                        {
                            lockTaken = true;
                            return;
                        }
#else
                        if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                        {
                            return;
                        }
#endif

#if !FEATURE_CORECLR
                        Thread.EndCriticalRegion();
#endif
                    }
                }
            }

            // Check the timeout.
            if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
            {
                DecrementWaiters();
                return;
            }

            //*** Step 3, Yielding
            //Sleep(1) every 50 yields
            int yieldsoFar = 0;
            while (true)
            {
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
#if !FEATURE_CORECLR
                    Thread.BeginCriticalRegion();
#endif
                    int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                                   observedOwner | 1                     // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                           : (observedOwner - 2) | 1;                    // otherwise decrement the waiters and set the lock bit
                    Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#if PFX_LEGACY_3_5
                    if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
                    {
                        lockTaken = true;
                        return;
                    }
#else
                    if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                    {
                        return;
                    }
#endif

#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
#endif
                }

                if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
                {
                    Thread.Sleep(1);
                }
                else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
                {
                    Thread.Sleep(0);
                }
                else
                {
#if PFX_LEGACY_3_5
                    Platform.Yield();
#else
                    Thread.Yield();
#endif
                }

                if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
                {
                    //Check the timeout.
                    if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
                    {
                        DecrementWaiters();
                        return;
                    }
                }

                yieldsoFar++;
            }
        }
示例#3
0
        /// <summary>
        /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
        /// 32-bit signed integer to measure the time interval, while observing a <see
        /// cref="T:System.Threading.CancellationToken"/>.
        /// </summary>
        /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
        /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
        /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
        /// observe.</param>
        /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
        /// false.</returns>
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
        /// negative number other than -1, which represents an infinite time-out.</exception>
        /// <exception cref="T:System.InvalidOperationException">
        /// The maximum number of waiters has been exceeded.
        /// </exception>
        /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
        /// name="cancellationToken"/> was canceled.</exception>
        public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
        {
            ThrowIfDisposed();
            cancellationToken.ThrowIfCancellationRequested(); // an early convenience check

            if (millisecondsTimeout < -1)
            {
                throw new ArgumentOutOfRangeException("millisecondsTimeout");
            }

            if (!IsSet)
            {
                if (millisecondsTimeout == 0)
                {
                    // For 0-timeouts, we just return immediately.
                    return(false);
                }


                // We spin briefly before falling back to allocating and/or waiting on a true event.
                uint startTime = 0;
                bool bNeedTimeoutAdjustment  = false;
                int  realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary.

                if (millisecondsTimeout != Timeout.Infinite)
                {
                    // We will account for time spent spinning, so that we can decrement it from our
                    // timeout.  In most cases the time spent in this section will be negligible.  But
                    // we can't discount the possibility of our thread being switched out for a lengthy
                    // period of time.  The timeout adjustments only take effect when and if we actually
                    // decide to block in the kernel below.

                    startTime = TimeoutHelper.GetTime();
                    bNeedTimeoutAdjustment = true;
                }

                //spin
                int HOW_MANY_SPIN_BEFORE_YIELD   = 10;
                int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5;
                int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20;

                int spinCount = SpinCount;
                for (int i = 0; i < spinCount; i++)
                {
                    if (IsSet)
                    {
                        return(true);
                    }

                    else if (i < HOW_MANY_SPIN_BEFORE_YIELD)
                    {
                        if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2)
                        {
#if PFX_LEGACY_3_5
                            Platform.Yield();
#else
                            Thread.Yield();
#endif
                        }
                        else
                        {
                            Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i));
                        }
                    }
                    else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0)
                    {
                        Thread.Sleep(1);
                    }
                    else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0)
                    {
                        Thread.Sleep(0);
                    }
                    else
                    {
#if PFX_LEGACY_3_5
                        Platform.Yield();
#else
                        Thread.Yield();
#endif
                    }

                    if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }

                // Now enter the lock and wait.
                EnsureLockObjectCreated();

                // We must register and deregister the token outside of the lock, to avoid deadlocks.
                using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this))
                {
                    lock (m_lock)
                    {
                        // Loop to cope with spurious wakeups from other waits being canceled
                        while (!IsSet)
                        {
                            // If our token was canceled, we must throw and exit.
                            cancellationToken.ThrowIfCancellationRequested();

                            //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
                            if (bNeedTimeoutAdjustment)
                            {
                                realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
                                if (realMillisecondsTimeout <= 0)
                                {
                                    return(false);
                                }
                            }

                            // There is a ---- that Set will fail to see that there are waiters as Set does not take the lock,
                            // so after updating waiters, we must check IsSet again.
                            // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the
                            // read from IsSet.  This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange
                            // operation which provides a full memory barrier.
                            // If we see IsSet=false, then we are guaranteed that Set() will see that we are
                            // waiting and will pulse the monitor correctly.

                            Waiters = Waiters + 1;

                            if (IsSet)     //This check must occur after updating Waiters.
                            {
                                Waiters--; //revert the increment.
                                return(true);
                            }

                            // Now finally perform the wait.
                            try
                            {
                                // ** the actual wait **
                                if (!Monitor.Wait(m_lock, realMillisecondsTimeout))
                                {
                                    return(false); //return immediately if the timeout has expired.
                                }
                            }
                            finally
                            {
                                // Clean up: we're done waiting.
                                Waiters = Waiters - 1;
                            }

                            // Now just loop back around, and the right thing will happen.  Either:
                            //     1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait)
                            // or  2. the wait was successful. (the loop will break)
                        }
                    }
                }
            } // automatically disposes (and deregisters) the callback

            return(true); //done. The wait was satisfied.
        }