多线程的常见用法详解 --转载

1、多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术。据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的。不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解。

2、多线程的使用:

(1)最简单、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});这种用法应该大多数人都使用过,参数为一个ThreadStart类型的委托。将ThreadStart转到定义可知:

public delegate void ThreadStart();

它是一个没有参数,没有返回值的委托。所以他的使用如下:

static void Main(string[] args)
{
   Thread oGetArgThread = new Thread(new ThreadStart(Test));
    oGetArgThread.IsBackground = true;
    oGetArgThread.Start();   

    for (var i = 0; i < 1000000; i++)
    {
      Console.WriteLine("主线程计数" + i);
      //Thread.Sleep(100);
    }

}

private static void Test()
 {
       for (var i = 0; i < 1000000; i++)
       {
           Console.WriteLine("后台线程计数" + i);
           //Thread.Sleep(100);
       }
 }

定义一个没有参数没有返回值的方法传入该委托。当然也可以不定义方法写成匿名方法:

        static void Main(string[] args)
        {
            Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() =>
            {

                for (var i = 0; i < 1000000; i++)
                {
                    Console.WriteLine("后台线程计数" + i);
                    //Thread.Sleep(100);
                }
            }));
            oGetArgThread.IsBackground = true;
            oGetArgThread.Start();

这个和上面的意义相同。得到的结果如下:

说明主线程和后台线程是互相独立的。由系统调度资源去执行。

如果这样那有人就要问了,如果我需要多线程执行的方法有参数或者有返回值或者既有参数又有返回值呢。。。别着急我们来看看new Thread()的几个构造函数:

public Thread(ParameterizedThreadStart start);
        public Thread(ThreadStart start);
        public Thread(ParameterizedThreadStart start, int maxStackSize);
        public Thread(ThreadStart start, int maxStackSize);

转到定义可知参数有两类,一类是无参无返回值的委托,另一类是有参无返回值的委托。对于有参数的委托使用方法:

    static void Main(string[] args)
        {
            Thread oThread = new Thread(new ParameterizedThreadStart(Test2));
            oThread.IsBackground = true;
            oThread.Start();
         }

     private static void Test2(object Count)
        {
            for (var i = 0; i < (int)Count; i++)
            {
                Console.WriteLine("后台线程计数" + i);
                //Thread.Sleep(100);
            }
        }    

对于有参又有返回值的委托,很显然使用new Thread()这种方式是没有解决方案的。其实对于有参又有返回值的委托可以使用异步来实现:

public delegate string MethodCaller(string name);//定义个代理
MethodCaller mc = new MethodCaller(GetName);
string name = "my name";//输入参数
IAsyncResult result = mc.BeginInvoke(name,null, null);
string myname = mc.EndInvoke(result);//用于接收返回值 

public string GetName(string name)    // 函数
{
    return name;
}    

关于这种方式还有几点值得一说的是:

①Thread oGetArgThread = new Thread(new ThreadStart(Test));

oGetArgThread.Join();//主线程阻塞,等待分支线程运行结束,这一步看功能需求进行选择,主要为了多个进程达到同步的效果

②线程的优先级可以通过Thread对象的Priority属性来设置,Priority属性对应一个枚举:

public enum ThreadPriority
    {
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之后。
        Lowest = 0,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
        BelowNormal = 1,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。
        //     默认情况下,线程具有 Normal 优先级。
        Normal = 2,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
        AboveNormal = 3,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之前。
        Highest = 4,
    }

从0到4,优先级由低到高。

③关于多个线程同时使用一个对象或资源的情况,也就是线程的资源共享,为了避免数据紊乱,一般采用.Net悲观锁lock的方式处理。

     private static object oLock = new object();
        private static void Test2(object Count)
        {
            lock (oLock)
            {
                for (var i = 0; i < (int)Count; i++)
                {
                    Console.WriteLine("后台线程计数" + i);
                    //Thread.Sleep(100);
                }
            }
        }

(2)Task方式使用多线程:这种方式一般用在需要循环处理某项业务并且需要得到处理后的结果。使用代码如下:

List<Task> lstTaskBD = new List<Task>();
foreach (var bd in lstBoards)
    {
         var bdTmp = bd;//这里必须要用一个临时变量
         var oTask = Task.Factory.StartNew(() =>
         {
              var strCpBdCmd = "rm -Rf " + bdTmp.Path + "/*;cp -R " + CombineFTPPaths(FTP_EMULATION_BD_ROOT,

"bd_correct") + "/* " + bdTmp.Path + "/";
              oPlink.Run(bdTmp.EmulationServer.BigIP, bdTmp.EmulationServer.UserName, bdTmp.EmulationServer.Password,

strCpBdCmd);
              Thread.Sleep(500);
           });
           lstTaskBD.Add(oTask);
    }
Task.WaitAll(lstTaskBD.ToArray());//等待所有线程只都行完毕

使用这种方式的时候需要注意这一句 var bdTmp = bd;这里必须要用一个临时变量,要不然多个bd对象容易串数据。如果有兴趣可以调试看看。这种方法比较简单,就不多说了。当然Task对象的用法肯定远不止如此,还涉及到任务的调度等复杂的逻辑。博主对这些东西理解有限,就不讲解了。

(3)线程池的用法:一般由于考虑到服务器的性能等问题,保证一个时间段内系统线程数量在一定的范围,需要使用线程池的概念。大概用法如下:

  public class CSpiderCtrl
    {

      //将线程池对象作为一个全局变量
        static Semaphore semaphore;

        public static void Run()
        {
            //1. 创建 SuperLCBB客户端对象
            var oClient = new ServiceReference_SuperLCBB.SOAServiceClient();

       //2.初始化的时候new最大的线程池个数255(这个数值根据实际情况来判断,如果服务器上面的东西很少,则可以设置大点)
            semaphore = new Semaphore(250, 255);

            CLogService.Instance.Debug("又一轮定时采集...");

            _TestBedGo(oClient);

        }

    //执行多线程的方法

    private static void _TestBedGo(ServiceReference_SuperLCBB.SOAServiceClient oClient)
        {
            List<string> lstExceptPDUs = new List<string>(){
                "SUPERLABEXP"
            };
            var oTestBedRes = oClient.GetTestBedExceptSomePDU(lstExceptPDUs.ToArray(), true);
            if (CKVRes.ERRCODE_SUCCESS != oTestBedRes.ErrCode)
            {
                CLogService.Instance.Error("xxx");
                return;
            }

            var lstTestBed = oTestBedRes.ToDocumentsEx();

            System.Threading.Tasks.Parallel.ForEach(lstTestBed, (oTestBed) =>
            {

         //一次最多255个线程,超过255的必须等待线程池释放一个线程出来才行
                semaphore.WaitOne();

                //CLogService.Instance.Info("开始采集测试床:" + oTestBed[TBLTestBed.PROP_NAME]);
                //Thread.Sleep(2000);

                var strTestBedName = oTestBed[TBLTestBed.PROP_NAME] as string;
                var strSuperDevIP = oTestBed[TBLTestBed.PROP_SUPERDEVIP] as string;
                var strTestBedGID = oTestBed[TBLTestBed.PROP_GID] as string;
                var strPdu = oTestBed[TBLTestBed.PROP_PDUGID] as string;
                Thread.Sleep(new Random().Next(1000, 5000));
                var oGetRootDevicesByTestBedGIDRes = oClient.GetRootDevicesByTestBedGID(strTestBedGID);
                CLogService.Instance.Debug(strPdu + "——测试床Name:" + strTestBedName + "开始");
                Stopwatch sp = new Stopwatch();
                sp.Start();
                if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < 2)
                {
                    CLogService.Instance.Debug("shit -- 3实验室中测试床Name:" + strTestBedName + "2完成异常0");

       //这里很重要的一点,每一次return 前一定要记得释放线程,否则这个一直会占用资源
                    semaphore.Release();
                    return;
                }

                var strXML = oGetRootDevicesByTestBedGIDRes.Documents[0];
                var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[1];
                //var strExeName = "RateSpider";

                var oSuperDevClient = new SuperDevClient(CSuperDev.ENDPOINT, string.Format(CSuperDev.SuperDevURL, strSuperDevIP));
                try
                {
                    oSuperDevClient.IsOK();
                }
                catch (Exception)
                {
                    CLogService.Instance.Error("测试床Name:" + strTestBedName + "异常,插件没起");
                    semaphore.Release();
                    return;
                }

                //2.3.1.请求SuperDev.Server(SuperDevIP),发送Run(XML和Exename)
                var oRunExeRes = new CKVRes();
                try
                {
                    oRunExeRes = oSuperDevClient.RunExeEx(strExeName, false, new string[] { strXML });
                }
                catch
                {
                    //CLogService.Instance.Debug("测试床Name:" + strTestBedName + "异常:" + ex.Message);
                }
                sp.Stop();
                CLogService.Instance.Debug(strPdu + "——测试床Name:" + strTestBedName + "完成时间" + sp.Elapsed);

          //每一个线程完毕后记得释放资源
                semaphore.Release();
            });
        }

   }

需要注意:Semaphore对象的数量需要根据服务器的性能来设定;System.Threading.Tasks.Parallel.ForEach这种方式表示同时启动lstTestBed.Length个线程去做一件事情,可以理解为

foreach(var oTestbed in lstTestBed)
{
        Thread oThread=new Thread(new ThreadStart({   ...}));
}

(4) 多线程里面还有一个值得一说的SpinWait类,用于提供对基于自旋的等待的支持。也就是说支持重复执行一个委托,知道满足条件就返回,我们来看它的用法:

        public static void SpinUntil(Func<bool> condition);

        public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout);

        public static bool SpinUntil(Func<bool> condition, TimeSpan timeout);

这个方法有三个构造函数,后两个需要传入一个时间,表示如果再规定的时间内还没有返回则自动跳出,防止死循环。

            SpinWait.SpinUntil(() =>
                    {
                        bIsworking = m_oClient.isworking(new isworking()).result;
                        return bIsworking == false;
                    }, 600000);
                    //如果等了10分钟还在跳纤则跳出
                    if (bIsworking)
                    {
                        oRes.ErrCode = "false交换机跳纤时间超过10分钟,请检查异常再操作";
                        return oRes;
                    }

博主使用过的多线程用法大概就这么三大类,当然这些其中还涉及很多细节性的东西,博主原来使用这些的时候经常出现各种莫名的问题,可能还是没用好的原因,对这些东西理解还不够深刻。如果大家也遇到类似的问题可以拿出来探讨!!

转载于:http://www.cnblogs.com/landeanfen/p/4704139.html

时间: 2024-08-24 02:14:42

多线程的常见用法详解 --转载的相关文章

C#基础系列——多线程的常见用法详解

前言:前面几节分别介绍了下C#基础技术中的反射.特性.泛型.序列化.扩展方法.Linq to Xml等,这篇跟着来介绍下C#的另一基础技术的使用.最近项目有点紧张,所以准备也不是特别充分.此篇就主要从博主使用过的几种多线程的用法从应用层面大概介绍下.文中观点都是博主个人的理解,如果有不对的地方望大家指正~~ 1.多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这

多线程的常见用法详解

前言:此篇就主要从博主使用过的几种多线程的用法从应用层面大概介绍下.文中观点都是博主个人的理解,如果有不对的地方望大家指正~~ 1.多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解. 2.多线程的使用: (1)最简单.最原始的使用方法:Thread oGetArgThread = new Thread(new

FileUpload上传控件用法详解 (转载)

FileUpload 类显示一个文本框控件和一个浏览按钮,使用户可以选择客户端上的文件并将它上载到 Web 服务器.用户通过在控件的文本框中输入本地计算机上文件的完整路径(例如,C:\MyFiles\TestFile.txt )来指定要上载的文件.用户也可以通过单击“浏览” 按钮,然后在“选择文件” 对话框中定位文件来选择文件. 注意: FileUpload 控件设计为仅用于部分页面呈现期间的回发情况,并不用于异步回发情况.在 UpdatePanel 控件内部使用 FileUpload 控件时,

VC++中MessageBox的常见用法详解

消息框是个很常用的控件,属性比较多,本文列出了它的一些常用方法,及指出了它的一些应用场合.         1.MessageBox("这是一个最简单的消息框!");         2.MessageBox("这是一个有标题的消息框!","这是标题");         3.MessageBox("这是一个确定 取消的消息框!","这是标题", MB_OKCANCEL );         4.Messa

const和typedef的常见用法详解

一.说说const 一般而言,const主要是用来防止定义的对象再次被修改,定义对象变量时要初始化变量. 常见用法如下: 1.用于定义常量变量,这样这个变量在后面就不可以再被修改 const int Val = 10; //Val = 20; //错误,不可被修改 2. 保护传参时参数不被修改,如果使用引用传递参数或按地址传递参数给一个函数,在这个函数里这个参数的值若被修改, 则函数外部传进来的变量的值也发生改变,若想保护传进来的变量不被修改,可以使用const保护 void  fun1(con

[js高手之路] es6系列教程 - promise常见用法详解(resolve,reject,catch,then,all,race)

关于promise我在之前的文章已经应用过好几次,如[js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist,本文就来讲解下promise的常见用法. 为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在项目中常用的静态方法:all, race, reject,resolve等,原

jar命令的用法详解(转载)

本文详细讲述了JAR命令的用法,对于大家学习和总结jar命令的使用有一定的帮助作用.具体如下: JAR包是Java中所特有一种压缩文档,其实大家就可以把它理解为.zip包.当然也是有区别的,JAR包中有一个META-INF\MANIFEST.MF文件,当你找成JAR包时,它会自动生成. JAR包是由JDK安装目录\bin\jar.exe命令生成的,当我们安装好JDK,设置好path路径,就可以正常使用jar.exe命令,它会用lib\tool.jar工具包中的类.这些细节就不用管它了. 让我们看

C#中string.format用法详解 [转载]

这篇文章主要介绍了C#中string.format用法,以实例形式较为详细的讲述了string.format格式化的各种用法,非常具有实用价值,需要的朋友可以参考下 本文实例总结了C#中string.format用法.分享给大家供大家参考.具体分析如下: String.Format 方法的几种定义: String.Format (String, Object) 将指定的 String 中的格式项替换为指定的 Object 实例的值的文本等效项.String.Format (String, Obj

LVM2常见用法详解

1.简介 LVM(Logical Volume Manager)是逻辑卷管理的意思,是linux环境下对磁盘分区进行管理的一种机制,lvm是建立在硬盘和分区之上的一个逻辑层,来提高磁盘分区管理的灵活性. 2.特点 可以对磁盘进行动态管理,动态增加或较少逻辑卷大小,而不会丢失现有的数据. 3.基本的逻辑管理概念 PV(physical volume)- 物理卷 通常是一个分区,也可以是整个物理硬盘,也可以是raid设备. VG(volume group)- 卷组 多个物理卷的集合,创建在物理卷之上