近期关于Thread使用的一些感想.

最近项目一直在研究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,会一直占用着,等到下一个请求到来,并且线程数达到最大时,才会主动回收。

原文地址:https://www.cnblogs.com/gavinhuang/p/2960454.html