多處理器的線程依賴
摘要
本文結合一個有趣的例子示範了如何設定CPU的線程依賴,並解釋了物理線程和邏輯線程的區別和聯絡。
Keywords
CPU, Thread, ProcessThread, C#
首先要搞清楚的是,Managed 程式碼中的線程(Thread)並不是真的線程(OS Thread)。它只是邏輯上我們劃定的線程。邏輯上的線程並不執行,實際執行的是物理線程或者叫OS線程。而通常來說,每一個邏輯線程都對應的關聯於某一條物理線程,這種關係一直維繫到該邏輯線程的生命週期結束。要設定線程的處理器依賴我們先要得到當前實際啟動並執行物理OS Thread或者叫物理線程,這時候需要使用Windows API – GetCurrentThreadId 。
class KernalHelper
{
[DllImport("kernel32")]
public static extern int GetCurrentThreadId();
}
然後再通過當前的osThreadID找到對應的ProcessThread,並設定其ProcessorAffinity。
以下代碼示範了如何在一台雙核PC的資源管理員裡划出正弦曲線:
private fields#region private fields
private static int rounds = 0;
private static int MAXROUND = 60;
private static long[] sins = new long[MAXROUND];
private static PerformanceCounter pc;
private static bool tag = false;
#endregion
static void Main(string[] args)
{
Thread WatchThread = new Thread(() => GenerateSin(1));
WatchThread.Start();
Console.WriteLine("Press any key to break");
Console.ReadKey();
WatchThread.Abort();
}
public static void GenerateSin(int num)
{
//對當前物理線程設定處理器依賴
int osThreadId = KernalHelper.GetCurrentThreadId();
foreach (ProcessThread pt in Process.GetCurrentProcess().Threads)
{
if (osThreadId == pt.Id)
{
pt.ProcessorAffinity = (IntPtr)num;
}
}
try
{
tag = true;
//獲得處理器的使用率
pc = new PerformanceCounter("Processor", "% Processor Time", "_Total");
for (int a = 0; a < MAXROUND; a++)
{
//六十個點的,振幅為5000000周期為60的正弦序列
sins[a] = (long)(5000000 * Math.Sin(Math.PI * a / (double)MAXROUND * 2f)) + 5000000;
}
long t1, t2;
long TimeSlide;
while (tag)
{
t1 = pc.RawValue + sins[rounds % MAXROUND];
TimeSlide = Stopwatch.GetTimestamp() + Stopwatch.Frequency;
t2 = TimeSlide - Stopwatch.Frequency + sins[rounds % MAXROUND] * Stopwatch.Frequency / 10000000;
while (pc.RawValue < t1)
{
Thread.Sleep(10);
}
while (Stopwatch.GetTimestamp() < TimeSlide) ;
rounds++;
}
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
finally
{
pc.Close();
}
}
效果:
關於更多控制CPU使用率的有趣思考請看<編程之美 - 微軟技術面試心得>
邏輯線程對CPU而言並不直接具有任務的性質,它只是一個CLR層面的資料結構,並不是OS層面實際的使用中的物件,邏輯線程與OS物理線程的依賴關係由CLR維護。這確實有一點點抽象,不過在託管的應用程式層面你基本上不需要關心你定義的線程和底層的線程是如何映射的。因為OS的線程調度能力比人工額外編碼控制要好得多,除非你確實是有非常特殊的任務需要做,或者需要硬體層面的記憶體映射,否則就不要更改進程和處理器的依賴關係以及託管線程和OS線程的依賴關係。我不知道為什麼.NET Framework中還提供了相應的方法和屬性協助程式員實現這樣的既不“優雅”也不“安全”的功能,這顯得和.NET一貫的作風有些不搭調。
預設情況下CLR並不保證每一個線程都始終運行在同一個處理器上,很可能在下一個時間片上線程會交由另一個處理器處理。為了避免這種情況,只能顯式的指定處理器依賴情況。不過一般來說,OS都會試圖保證所有UI線程都運行在發起它們的處理器上。所以還是覺得實際上除了這種趣味性的程式和某些極端測試,沒有必要對此進行人工幹涉。
Enjoy it~
黃季冬<fox23>