关于C#线程,线程池和并行运算的简单使用和对比
转自:https://www.cnblogs.com/jeffwongishandsome/archive/2010/11/12/1876137.html
先大概看一下控制台应用程序的Main方法的主要代码:
static bool done = false;
static decimal count2 = ;
static int threadDone = ;//标志启用线程数?
static System.Timers.Timer timer = new System.Timers.Timer(); static decimal[] threadPoolCounters = new decimal[];
static Thread[] threads = new Thread[];
static System.Timers.Timer[] threadTimers = new System.Timers.Timer[]; static void Main(string[] args)
{
timer.Stop();
/*当 AutoReset 设置为 false 时,Timer 只在第一个 Interval 过后引发一次 Elapsed 事件。
若要保持以 Interval 时间间隔引发 Elapsed 事件,请将 AutoReset 设置为 true。*/
timer.AutoReset = false;
timer.Elapsed += new ElapsedEventHandler(OnTimerEvent);//当timer.Start()时,触发事件
decimal total = ; // raw test
decimal count1 = SingleThreadTest();//单一线程,一跑到底
Console.WriteLine("Single thread count = " + count1.ToString()); // create one thread, increment counter, destroy thread, repeat
Console.WriteLine();
CreateAndDestroyTest();//创建一个线程,运算,然后销毁该线程 重复前面的动作
Console.WriteLine("Create and destroy per count = " + count2.ToString()); // Create 10 threads and run them simultaneously
//一次性创建10个线程,然后遍历使线程执行运算
Console.WriteLine();
InitThreadPoolCounters();
InitThreads();
StartThreads();
while (threadDone != ) { };
Console.WriteLine("10 simultaneous threads:");
for (int i = ; i < ; i++)
{
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString());
Console.WriteLine(); Console.WriteLine("///////////////////////////////////////////////////"); // using ThreadPool
//直接通过线程池的QueueUserWorkItem方法,按队列执行10个任务
Console.WriteLine();
Console.WriteLine("ThreadPool:");
InitThreadPoolCounters();
QueueThreadPoolThreads();
while (threadDone != ) { };
Console.WriteLine("ThreadPool: 10 simultaneous threads:");
total = ;
for (int i = ; i < ; i++)
{
// threadTimers[i].Stop();
// threadTimers[i].Dispose();
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString()); // using SmartThreadPool
//通过Amir Bar的SmartThreadPool线程池,利用QueueUserWorkItem方法,按队列执行10个任务
Console.WriteLine();
Console.WriteLine("SmartThreadPool:");
InitThreadPoolCounters();
QueueSmartThreadPoolThreads();
while (threadDone != ) { };
Console.WriteLine("SmartThreadPool: 10 simultaneous threads:");
total = ;
for (int i = ; i < ; i++)
{
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString()); // using ManagedThreadPool
//通过Stephen Toub改进后的线程池,利用QueueUserWorkItem方法,按队列执行10个任务
Console.WriteLine();
Console.WriteLine("ManagedThreadPool:");
InitThreadPoolCounters();
QueueManagedThreadPoolThreads();
while (threadDone != ) { };
Console.WriteLine("ManagedThreadPool: 10 simultaneous threads:");
total = ;
for (int i = ; i < ; i++)
{
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString()); // using C#4.0 Parallel
//通过Tasks.Parallel.For进行并行运算
Console.WriteLine();
Console.WriteLine("Parallel:");
InitThreadPoolCounters();
UseParallelTasks();
while (threadDone != ) { };
Console.WriteLine("Parallel: 10 simultaneous threads:");
total = ;
for (int i = ; i < ; i++)
{
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString());
}
我们可以先熟悉一下大致思路。代码中,我们主要依靠输出的数字count或者total来判断哪个方法执行效率更高(原文是How Hign Can I Count?),通常输出的数字越大,我们就认为它”干的活越多“,效率越高。主要实现过程就是通过一个静态的System.Timers.Timer对象的timer实例,设置它的Interval属性和ElapsedEventHandler事件:
static System.Timers.Timer timer = new System.Timers.Timer();
/*当 AutoReset 设置为 false 时,Timer 只在第一个 Interval 过后引发一次 Elapsed 事件。
若要保持以 Interval 时间间隔引发 Elapsed 事件,请将 AutoReset 设置为 true。*/
timer.AutoReset = false;
timer.Elapsed += new ElapsedEventHandler(OnTimerEvent);//当timer.Start()时,触发事件
其中,timer的事件触发的函数:
static void OnTimerEvent(object src, ElapsedEventArgs e)
{
done = true;
}
每次timer.Start执行的时候,一次测试就将开始,这样可以确保测试的不同方法都在1000毫秒内跑完。
下面开始具体介绍几个方法:
A、线程
这个非常简单,就是通过主线程计算在1000毫秒内,count从0递增加到了多少:
/// <summary>
/// 单一线程,一跑到底
/// </summary>
/// <returns></returns>
static decimal SingleThreadTest()
{
done = false;
decimal counter = ;
timer.Start();
while (!done)
{
++counter;
}
return counter;
}
while判断可以保证方法在1000毫秒内执行完成。
B、多线程
这个多线程方法比较折腾,先创建线程,然后运行,最后销毁线程,这就是一个线程执行单元,重复10次这个线程执行单元。
/// <summary>
/// 创建一个线程,运算,然后销毁该线程 重复前面的动作
/// </summary>
static void CreateAndDestroyTest()
{
done = false;
timer.Start();
while (!done)
{
Thread counterThread = new Thread(new ThreadStart(Count1Thread));
counterThread.IsBackground = true;//后台线程
counterThread.Start();
while (counterThread.IsAlive) { };
}
}
那个ThreadStart委托对应的方法Count1Thread如下:
static void Count1Thread()
{
++count2; //静态字段count2自增
}
从表面上看,大家估计都可以猜到,效果可能不佳。
C、还是多线程
这个方法不判断线程的执行状态,不用等到一个线程销毁后再创建一个线程,然后执行线程方法。线程执行的方法就是根据线程的Name找到一个指定数组的某一索引,并累加改变数组的值:
/// <summary>
/// 将数组和线程数标志threadDone回到初始状态
/// </summary>
static void InitThreadPoolCounters()
{
threadDone = ;
for (int i = ; i < ; i++)
{
threadPoolCounters[i] = ;
}
} /// <summary>
/// 初始化10个线程
/// </summary>
static void InitThreads()
{
for (int i = ; i < ; i++)
{
threads[i] = new Thread(new ThreadStart(Count2Thread));
threads[i].IsBackground = true;
threads[i].Name = i.ToString();//将当前线程的Name赋值为数组索引,在Count2Thread方法中获取对应数组
}
} /// <summary>
/// 开始多线程运算
/// </summary>
static void StartThreads()
{
done = false;
timer.Start();
for (int i = ; i < ; i++)
{
threads[i].Start();
}
}
其中,每一个线程需要执行的委托方法
static void Count2Thread()
{
int n = Convert.ToInt32(Thread.CurrentThread.Name);//取数组索引
while (!done)
{
++threadPoolCounters[n];
}
Interlocked.Increment(ref threadDone);//以原子操作的形式保证threadDone递增
}
在测试过程中,我们看代码:
// Create 10 threads and run them simultaneously
//一次性创建10个线程,然后遍历使线程执行运算
Console.WriteLine();
InitThreadPoolCounters();
InitThreads();
StartThreads();
while (threadDone != ) { };
Console.WriteLine("10 simultaneous threads:");
for (int i = ; i < ; i++)
{
Console.WriteLine("T" + i.ToString() + " = " + threadPoolCounters[i].ToString() + " ");
total += threadPoolCounters[i];
}
Console.WriteLine("Total = " + total.ToString());
Console.WriteLine();
最后算出这个数组的所有元素的总和,就是这10个线程在1000毫秒内所做的事情。其中, while (threadDone != 10) { };这个判断非常重要。这个方法看上去没心没肺,线程创建好就不管它的死活了(还是管活不管死?),所以效率应该不低。
实际上,我在本地测试并看了一下输出,表面看来,按count大小逆序排列:C>A>B,这就说明多线程并不一定比单线程运行效率高。其实B之所以效率不佳,主要是由于这个方法大部分的”精力“花在线程的执行状态和销毁处理上。
注意,其实C和A、B都没有可比性,因为C计算的是数组的总和,而A和B只是简单的对一个数字进行自加。
ps:C这一块说的没有中心,想到哪写到哪,所以看起来写得很乱,如果看到这里您还觉着不知所云,建议先下载最后的demo,先看代码,再对照这篇文章。
好了,到这里,我们对线程的创建和使用应该有了初步的了解。细心的人可能会发现,我们new一个Thread,然后给线程实例设置属性,比如是否后台线程等等,其实这部分工作可以交给下面介绍的线程池ThreadPool来做(D、E和F主要介绍线程池)。
D、线程池ThreadPool
在实际的项目中大家可能使用最多最熟悉的就是这个类了,所以没什么可说的:
/// <summary>
/// ThreadPool测试
/// </summary>
static void QueueThreadPoolThreads()
{
done = false;
for (int i = ; i < ; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Count3Thread), i);
} timer.Start();
} static void Count3Thread(object state)
{
int n = (int)state;
while (!done)
{
++threadPoolCounters[n];
}
Interlocked.Increment(ref threadDone);
}
我们知道线程池里的线程默认都是后台线程,所以它实际上简化了线程的属性设置,更方便异步编程。
需要说明的是,线程池使用过程中会有这样那样的缺陷(虽然本文的几个线程池任务都不会受这种缺陷影响)。比如,我们一次性向线程池中加入100个任务,但是当前的系统可能只支持25个线程,并且每个线程正处于”忙碌“状态,如果一次性加入池中系统会处理不过来,那么多余的任务必须等待,这就造成等待的时间过长,系统无法响应。还好,ThreadPool提供了GetAvailableThreads方法,可以让你知道当前可用的工作线程数量。
static void QueueThreadPoolThreads()
{
done = false;
for (int i = ; i < ; i++)
{
//ThreadPool.QueueUserWorkItem(new WaitCallback(Count3Thread), i); //直接给程序池添加任务有时是很草率的 WaitCallback wcb = new WaitCallback(Count3Thread);
int workerThreads, availabeThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out availabeThreads);
if (workerThreads > )//可用线程数>0
{
ThreadPool.QueueUserWorkItem(wcb, i);
}
else
{
//to do 可以采取一种策略,让这个任务合理地分配给线程
}
}
如果没有可用的工作线程数,必须设计一定的策略,让这个任务合理地分配给线程。
也许就是类似于上面那样的限制,很多开发者都自己创建自己的线程池,同时也就有了后面的SmartThreadPool和ManagedThreadPool大展身手的机会。
E、线程池SmartThreadPool
大名鼎鼎的SmartThreadPool,但是我从来没在项目中使用过,所以只是找了一段简单的代码测试一下:
/// <summary>
/// SmartThreadPool测试
/// </summary>
static void QueueSmartThreadPoolThreads()
{
SmartThreadPool smartThreadPool = new SmartThreadPool();
// Create a work items group that processes
// one work item at a time
IWorkItemsGroup wig = smartThreadPool.CreateWorkItemsGroup(); done = false;
timer.Start();
for (int i = ; i < ; i++)
{
wig.QueueWorkItem(new WorkItemCallback(Count4Thread), i);
}
// Wait for the completion of all work items in the work items group
wig.WaitForIdle();
smartThreadPool.Shutdown();
} static object Count4Thread(object state)
{
int n = (int)state;
while (!done)
{
++threadPoolCounters[n];
}
Interlocked.Increment(ref threadDone);
return null;
}
自从收藏这个SmartThreadPool.dll后,我还从没有在项目中使用过。查看它的源码注释挺少也挺乱的,不知道有没有高人知道它的一个效率更好的方法。您也可以看看英文原文,自己尝试体验一下。如果您熟悉使用SmartThreadPool,欢迎讨论。
F、线程池ManagedThreadPool
Stephen Toub这个完全用C#托管代码实现的线程池也非常有名,在Marc Clifton的英文原文中,作者也不吝溢美之词,赞它“quite excellent”,用当前异军突起的一个词汇形容就是太给力了,于我心有戚戚焉:
/// <summary>
/// ManagedThreadPool测试
/// </summary>
static void QueueManagedThreadPoolThreads()
{
done = false;
timer.Start();
for (int i = ; i < ; i++)
{
Toub.Threading.ManagedThreadPool.QueueUserWorkItem(new WaitCallback(Count5Thread), i);
}
}
static void Count5Thread(object state)
{
int n = (int)state;
while (!done)
{
++threadPoolCounters[n];
}
Interlocked.Increment(ref threadDone);
}
对于这个托管的线程池,我个人的理解,就是它在管理线程的时候,这个池里还有一个缓存线程的池,即一个ArrayList对象。它一开始就初始化了一定数量的线程,并通过ProcessQueuedItems方法保证异步执行进入池中的队列任务(那个死循环有时可能导致CPU过分忙碌),这样在分配异步任务的时候,就省去了频繁去创建(new)一个线程。同时它在实现信号量(Semaphore)的同步和线程出入队列的设计上都可圈可点,非常巧妙,强烈推荐您阅读它的源码。
G、并行运算
下面的示例,我只使用了简单的System.Threading.Tasks.Parallel.For 对应的for 循环的并行运算:
/// <summary>
/// 并行运算测试
/// </summary>
static void UseParallelTasks()
{
done = false;
timer.Start();
// System.Threading.Tasks.Parallel.For - for 循环的并行运算
System.Threading.Tasks.Parallel.For(, , (i) => { Count6Thread(i); });
}
static void Count6Thread(object state)
{
int n = (int)state;
while (!done)
{
++threadPoolCounters[n];
}
Interlocked.Increment(ref threadDone);
}
没有什么要特殊说明的,就是新类库的使用。看代码,好像比使用线程或线程池更加简单直接,有机会争取多用一用。我在本地测试的时候,在Release版本下,按照count的大小逆序排列,总体上G>D>F>E。需要注意到一件事,就是SmartThreadPool中排入队列的任务是一个返回值为Object的委托类型,这和其他的几个没有返回的(void类型)不同。SmartThreadPool口碑还是不错的,也许是我没有正确使用它。
最后小结一下:本文主要列举了C#中我所知道的几种常见的异步处理的方法,欢迎大家纠错或补充。
关于C#线程,线程池和并行运算的简单使用和对比的更多相关文章
- 【Android】线程池原理及Java简单实现
线程池简介 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为: T1 创建线程时间 T2 在线程中 ...
- GIL 线程/进程池 同步异步
GIL 什么是GIL 全局解释器锁,本质是一把互斥锁,是加在cpython解释器上的一把锁, 同一个进程内的所有线程需要先抢到GIL锁,才能执行python代码 为什么要有GIL cpython解释器 ...
- 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq
常量,字段,构造方法 常量 1.什么是常量 常量是值从不变化的符号,在编译之前值就必须确定.编译后,常量值会保存到程序集元数据中.所以,常量必须是编译器识别的基元类型的常量,如:Boolean ...
- 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发
子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...
- C#线程 线程进阶
第四部分:高级线程 非阻塞同步 前面我们说过,即使在分配或增加字段的简单情况下,也需要同步.尽管锁定始终可以满足此需求,但是竞争性锁定意味着线程必须阻塞,从而遭受上下文切换的开销和调度的延迟,这在高度 ...
- Linux线程的实现 & LinuxThread vs. NPTL & 用户级内核级线程 & 线程与信号处理
另,线程的资源占用可见:http://www.cnblogs.com/charlesblc/p/6242111.html 进程 & 线程的很多知识可以看这里:http://www.cnblog ...
- Linux线程 之 线程 线程组 进程 轻量级进程(LWP)
Thread Local Storage,线程本地存储,大神Ulrich Drepper有篇PDF文档是讲TLS的,我曾经努力过三次尝试搞清楚TLS的原理,均没有彻底搞清楚.这一次是第三次,我沉浸gl ...
- JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止
JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...
- python 进程和线程-线程和线程变量ThreadLocal
线程 线程是由若干个进程组成的,所以一个进程至少包含一个线程:并且线程是操作系统直接支持的执行单元.多任务可以由多进程完成,也可由一个进程的多个线程来完成 Python的线程是真正的Posix Thr ...
随机推荐
- 手机访问电脑中部署的tomcat应用
手机访问电脑中部署的tomcat应用. 操作步骤: 第一种:有无线路由的情况. 1.建议局域通信. 操作如下:电脑,手机都自动连接到无线路由器中(无线路由不必非要联网). 2.启动电脑用的tomcat ...
- 【Java并发编程】:死锁
当线程需要同时持有多个锁时,有可能产生死锁.考虑如下情形: 线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2.接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持 ...
- echart 桑基图操作事项
例图 注意: option = { label:{//formatter名字 show:true, formatter:function(obj){ return obj.data.name+'_12 ...
- chroot的用法
chroot命令用来在指定的根目录下运行指令.chroot,即 change root directory (更改 root 目录).在 linux 系统中,系统默认的目录结构都是以/,即是以根 (r ...
- centos7-安装mysql5.6.36
本地安装了mysql5.7, 但和springboot整合jpa时会出现 hibernateException, 不知道为什么, 换个mysql5.6版本的mysql, 源码安装, cmake一直过 ...
- Low Power之CPF/UPF
1 CPF The Common Power Format is a standard promoted by the Low Power Coalition at Si2. CPF is also ...
- SFTP服务器搭建
Sftp搭建文档 1. 查看openssh的版本 # ssh -V Openssh版本必须大于4.8p1. 2. 创建用户并设置登录密码 #groupadd sftp #useradd –d / ...
- ABP实战--集成Ladp/AD认证
参照Hunter的ABP-Zero模块中用户管理部分. 由于我们公司的各系统基本都是AD帐号登录的,所以我们需扩展ABP的AuthenticationSource. 添加MyLdapAuthentic ...
- 使用Quartz.net来执行定时任务
Quartz.net使用方法:http://www.cnblogs.com/lizichao1991/p/5707604.html 最近,项目中需要执行一个计划任务,组长就让我了解一下Quartz.n ...
- C# 获取config文件的值
自定义配置文件帮助类 利用ExeConfigurationFileMap类将自定义配置文件转换为Configuration类进行数据读取 代码很简单,就不做扼要说明 /// <summary&g ...