文章出處

出招

昨天對“黑色n秒”問題的最終猜想以失敗而告終,從而讓我們結束了被動猜想階段,進入了主動進攻階段——出招。

今天出第一招——用C#寫個小程序,讓其在每個CPU核上運行一個線程,不讓任何一個CPU核進入空閑(idle)狀態,以進一步排除CPU idle引起的“黑色n秒”。

在這一招中,借助的最重要的武器是System.Diagnostics.ProcessThread.ProcessorAffinity。通過給ProcessorAffinity設置一個掩碼,就可以指定當前線程運行于哪個CPU核上。

如上圖,用哪個核就把那個核對應的二進制位置1,其他位保持0。

所以對于我們所用的8核CPU,從第1核到第8核對應的ProcessorAffinity分別是:1, 2, 4, 8, 16, 32, 64, 128。

需要注意的地方是ProcessThread.ProcessorAffinity針對的是Windows操作系統線程,而.NET線程并不是與操作系統線程一一對應的,一個.NET線程可以運行于多個操作系統線程。所以,如果僅僅指定ProcessThread.ProcessorAffinity,并不能保證.NET線程運行于指定的CPU核上。那怎么辦呢?

微軟提供了解決方案,在設置ProcessThread.ProcessorAffinity之前需要通過下面的代碼將.NET線程固定在操作系統線程上:

Thread.BeginThreadAffinity();

還有一個需要解決的問題是如何讓一個線程一直處于執行狀態,從而不讓其所在的CPU核進入idle狀態。微軟也提供了解決方案,調用非托管方法SetThreadExecutionState(),代碼如下:

NativeMethods.SetThreadExecutionState(NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED);

下面請看招式:

代碼第1部分:

class Program
{
    static void Main(string[] args)
    {
        var threads = new Thread[Environment.ProcessorCount];
        Console.WriteLine("Processor Count:" + Environment.ProcessorCount);
        for (int i = 0; i < threads.Length; i++)
        {
            var coreNumber = i + 1;
            var threaName = "ThreadOnCPU" + coreNumber;
            var start = new ThreadStart(() =>
            {
                Console.WriteLine(threaName + " is working...");
                NativeMethods.SetThreadExecutionState(
                    NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED);
            });
            var thread = new DistributedThread(start);
            thread.ProcessorAffinity = (int)Math.Pow(2, i);
            thread.ManagedThread.Name = threaName;
            thread.Start();
        }
        Console.ReadKey();
    }       
}

internal static class NativeMethods
{
    [DllImport("kernel32.dll")]
    public static extern uint SetThreadExecutionState(uint esFlags);
    public const uint ES_CONTINUOUS = 0x80000000;
    public const uint ES_SYSTEM_REQUIRED = 0x00000001;
}

代碼第2部分(來自Running .NET threads on selected processor cores ):

class DistributedThread
{
    [DllImport("kernel32.dll")]
    public static extern int GetCurrentThreadId();

    [DllImport("kernel32.dll")]
    public static extern int GetCurrentProcessorNumber();

    private ThreadStart threadStart;

    private ParameterizedThreadStart parameterizedThreadStart;

    private Thread thread;

    public int ProcessorAffinity { get; set; }

    public Thread ManagedThread
    {
        get
        {
            return thread;
        }
    }

    private DistributedThread()
    {
        thread = new Thread(DistributedThreadStart);
    }

    public DistributedThread(ThreadStart threadStart)
        : this()
    {
        this.threadStart = threadStart;
    }

    public DistributedThread(ParameterizedThreadStart threadStart)
        : this()
    {
        this.parameterizedThreadStart = threadStart;
    }

    public void Start()
    {
        if (this.threadStart == null) throw new InvalidOperationException();

        thread.Start(null);
    }

    public void Start(object parameter)
    {
        if (this.parameterizedThreadStart == null) throw new InvalidOperationException();

        thread.Start(parameter);
    }

    private void DistributedThreadStart(object parameter)
    {
        try
        {
            // fix to OS thread
            Thread.BeginThreadAffinity();

            // set affinity
            if (ProcessorAffinity != 0)
            {
                CurrentThread.ProcessorAffinity = new IntPtr(ProcessorAffinity);
            }

            // call real thread
            if (this.threadStart != null)
            {
                this.threadStart();
            }
            else if (this.parameterizedThreadStart != null)
            {
                this.parameterizedThreadStart(parameter);
            }
            else
            {
                throw new InvalidOperationException();
            }
        }
        finally
        {
            // reset affinity
            CurrentThread.ProcessorAffinity = new IntPtr(0xFFFF);
            Thread.EndThreadAffinity();
        }
    }

    private ProcessThread CurrentThread
    {
        get
        {
            int id = GetCurrentThreadId();
            return
                (from ProcessThread th in Process.GetCurrentProcess().Threads
                    where th.Id == id
                    select th).Single();
        }
    }
}
DistributedThread

接下來,出招——KeepAllCpuCoresAlive!

KeepAllCpuCoresAlive

結果。。。這一招以失敗告終!

黑色1秒

(上圖是“黑色1秒”發生時性能監視器監測到的ASP.NET Requests/Sec為0的情況)

失敗又如何,就如同代碼編譯不通過一般不值一提。那為什么還要寫博客出來呢?分享的就是過程!

接下來呢?準備第二招。。。

【參考資料】

Running .NET threads on selected processor cores

Threading in C#

Keep Alive the Machine - No Sleep


文章列表


不含病毒。www.avast.com
全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()