多处理器的线程依赖

 

多处理器的线程依赖

摘要

本文结合一个有趣的例子演示了如何设置CPU的线程依赖,并解释了物理线程和逻辑线程的区别和联系。

Keywords

CPU, Thread, ProcessThread, C#

首先要搞清楚的是,托管代码中的线程(Thread)并不是真的线程(OS Thread)。它只是逻辑上我们划定的线程。逻辑上的线程并不执行,实际执行的是物理线程或者叫OS线程。而通常来说,每一个逻辑线程都对应的关联于某一条物理线程,这种关系一直维系到该逻辑线程的生命周期结束。要设置线程的处理器依赖我们先要得到当前实际运行的物理OS Thread或者叫物理线程,这时候需要使用Windows API – GetCurrentThreadId

    class KernalHelper
    
{
        [DllImport(
"kernel32")]
       
public static extern int GetCurrentThreadId(); 
    }

 

然后再通过当前的osThreadID找到对应的ProcessThread,并设定其ProcessorAffinity

以下代码演示了如何在一台双核PC的资源管理器里划出正弦曲线:

 private fields

        
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>


posted on 2008-06-02 22:35  J.D Huang  阅读(1253)  评论(3编辑  收藏  举报