最近项目一直在研究Thread问题,在客户端大量登陆时,或者大量请求时,会偶尔出现等待,甚至超时现象,和朋友通过性能监视器,查看服务器cpu,call per second等指标,发现在高峰时,cpu不稳定,线程加载慢,造成等待,通讯超时等问题,然后研究了一些关于Thread问题,得到了一些体会,感想.
1.Thread在达到设置的MinWorkerThreadsPerCore后,线程数量会以两个的两个加载,一般在启动服务前我们会先设置线程数,代码如下:
int minWorkerThreadsPerCore=100; int minIOThreadsPerCore=100; int maxWorkerThreadsPerCore=1000; int maxIOThreadsPerCore=1000; ThreadPool.SetMinThreads(minWorkerThreadsPerCore * Environment.ProcessorCount, minIOThreadsPerCore * Environment.ProcessorCount); ThreadPool.SetMaxThreads(maxWorkerThreadsPerCore * Environment.ProcessorCount, maxIOThreadsPerCore * Environment.ProcessorCount);
2.启动异步线程有多种方法.
(1)在创建托管的线程时,在该线程上执行的方法将通过一个传递给 Thread 构造函数的 ThreadStart 委托或 ParameterizedThreadStart 委托来表示。在调用 System.Threading.Thread.Start 方法之前,该线程不会开始执行。执行将从 ThreadStart 或 ParameterizedThreadStart 委托表示的方法的第一行开始。
Thread thread = new Thread(new ThreadStart(ThreadTestMethod)); thread.Start(); static void ThreadTestMethod() { Console.WriteLine("Test成功"); }
(2)调用 StartNew 在功能上等效于创建任务使用其构造函数之一来调用 Start 计划它的执行。
Task.Factory.StartNew(ThreadTestMethod);
(3)在线程池中执行
ThreadPool.QueueUserWorkItem(o => ThreadTestMethod());
(4)通过BeginInvoke,EndInvoke来调用,接收
var handle = new SumDelegate(Sum); IAsyncResult handleResut = handle.BeginInvoke(2, 3, null, null); var result = handle.EndInvoke(handleResut); public delegate int SumDelegate(int x, int y); static int Sum(int x, int y) { return x + y; }
(5)通过实体类嵌套委托
var test = new Test(); test.X = 4; test.Y = 5; test.MethodCompleted = new MethodCompleteCallback(WriteResult); Thread thread1 = new Thread(new ThreadStart(test.MyStartingMethod)); thread1.Start(); static void WriteResult(Test test) { Console.WriteLine(test.S); } public delegate void MethodCompleteCallback(Test test); public class Test { public int X { get; set; } public int Y { get; set; } public int S { get; set; } public MethodCompleteCallback MethodCompleted; public void MyStartingMethod() { this.S = this.X + this.Y; // 函数已经执行完了,调用另外一个函数。 this.MethodCompleted(this); } }
然后想到了一个问题,delegate如果参数和eventhandle一致也是object和EventArgs,能否相互转换,很可惜,微软未封装相关方法,经过网上资料查询,找到了Artech's blog中有提到相关转换,其具体思路是使用 DynamicMethod 类在运行时生成和执行方法
DynamicMethod method = new DynamicMethod("WrappedEventHandler", null, paramTypes); MethodInfo invoker = paramTypes[0].GetMethod("Invoke"); ILGenerator il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_2); if(!sourceParameters[1].ParameterType.IsAssignableFrom(destinationParameters[1].ParameterType)) { il.Emit(OpCodes.Castclass,sourceParameters[1].ParameterType); } il.Emit(OpCodes.Call, invoker); il.Emit(OpCodes.Ret); return method.CreateDelegate(eventHandlerType, eventHandler);
写了这么多,突然有一天想到了一个很奇怪的问题,多线程的本质究竟是什么?Thread.Sleep(0)有什么作用?
然后就研究了一下windows操作系统线程的优先级问题。
操作系统中Windows是一种所谓的抢占式操作系统,就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU ,直到操作系统发现某个线程长时间霸占CPU,会强制使这个线程挂起。
在抢占式操作系统中,假设有若干进程,操作系统会根据他们的优先级,给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一 次所有进程的总优先级,然后再挑一个优先级最高的把 CPU 控制权交给他。
因此,Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里。
而Thread.Sleep(2000)或以上时,又会发现一个新问题,当线程失效后,并不会马上gc,会一直占用着,等到下一个请求到来,并且线程数达到最大时,才会主动回收。