有关单线程想必不用在这里罗嗦;多线程常常应用在大量密集型事务处理、高并发以及性能亟待提升的敏感区域,好处不言而喻:充分利用CPU资源、提高吞吐、减少用户等待、同事增强了程序的灵活性,更有利于内存任务调度、数据交互、资源分配等; 但应用多线程,往往带来额外的复杂性,如:死锁、线程通讯、线程同步等等,暂且不用理会,具体问题具体分析便可。
不论是.NET还是Java, 都提供了相对成熟的线程模型,只要在使用中控制好:线程的同步性、线程在特定场景的通讯以及线程的安全性就可以了。本文介绍以下几个场景下应用多线程的示例,以供参考!
§ 场景1
远程服务数据查询接口,但碍于网络数据传输包大小的限制,每次只能查询最多N条数据,所以如果要显示或 提供100N条记录给用户就需要多次调用同一个接口,再进行封装为整体数据包给用户。
public class Triple<K, V>
{
public K First { get; set; }
public V Second { get; set; }
public Triple()
{ }
public Triple(K k, V v)
{
this.First = k;
this.Second = v;
}
}
public static List<Triple<Request, Exception>> TakeActionOn<Request>(this Action<Request> action, List<Request> requests)
where Request : class
{
List<Triple<Request, Exception>> answers = new List<Triple<Request, Exception>>();
using (WorkCountDown wcd = new WorkCountDown(requests.Count))
{
object syncLock = new object();
foreach (Request request in requests)
{
ThreadPool.QueueUserWorkItem(o =>
{
Request req = o as Request;
Triple<Request, Exception> answer = new Triple<Request, Exception>();
try
{
answer.First = req;
action(req);
}
catch (Exception ex)
{
ExceptionSendMgr.SendMonitor(ex);
answer.Second = ex;
}
finally
{
lock (syncLock)
{
answers.Add(answer);
}
wcd.CountDown();
}
}, request);
}
wcd.CountDown();
wcd.WaitAll();
}
return answers;
}
/// <summary>
/// Instead of WaitHandle.WaitAll's max to 64 limit
/// </summary>
class WorkCountDown : IDisposable
{
int totalCount;
private readonly ManualResetEvent mre;
public WorkCountDown(int totalWorkCount)
{
totalCount = totalWorkCount + 1;
mre = new ManualResetEvent(false);
}
public void CountDown()
{
if (Interlocked.Decrement(ref totalCount) <= 0)
{
mre.Set();
}
}
/// <summary>
/// always remeber to call an extra CountDown before waitAll
/// </summary>
public void WaitAll()
{
mre.WaitOne();
}
#region IDisposable 成员
public void Dispose()
{
((IDisposable)mre).Dispose();
}
#endregion
}
同时对于扩展参数:Action<Request> action,内部处理也需要关注线程安全、同步异步操作,当然可通过ThreadPool.SetMaxThreads来控制线程池大小。
§ 场景2
多线程下载,这块的应用相对复杂一些,总体来说就是:
根据文件大小,对文件进行分块,同时通过建立对应的多个通道,进行并发下载,大大的提升下载效率,在下载过程主要存在的问题是:
1、 块下载状态的保存,存在下载暂停、中断等状况
2、 块合并
3、 各线程执行状况汇总,以便更新下载进度、下载报告(线程同步)
4、动态增加线程数
§多线程扩展
1、 ManualResetEvent
通常ThreadPool中的工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行,有关ManualResetEvent:
可以将布尔值传递给构造函数来初始化ManualResetEvent 的初始状态,true为终止状态;否则为 false。
在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:
Reset()方法:将其设置为无信号状态;
Set()方法:将其设置为有信号状态。
WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作
2、InterLocked(线性):提供一些有用的原子操作,跟lock关键字在本质上是一样的,但是Lock为线性排他锁,所以不适宜关注性能的多线程并发处理,也算是悲观类型锁
3、互斥对象Mutex(线性)
写过WIN32 App的朋友应该不会陌生的是,在APP启动时只允许一个实例运行时,比较简洁的处理方式也是通过Mutex信号量来实现
进程之内或之间的线程需要访问操作系统的资源的时候,需要一个控制机制来限制资源访问的冲突都可以采用Mutex,同一时间,只能有一个线程占用Mutex。访问资源之前,每个线程都通过发信号,以获得Mutex的控制权。此后,线程还必须等待资源的控制权。当线程完成操作时,通过ReleaseMutex()发出完成信号( lock和Monitor对于unmanaged 资源是不起作用的)
Mutex objMutex = new Mutex(false, "ThreadLock" );
objMutex.WaitOne();
//代码
objMutex.ReleaseMutex();
适当的场景如果选择Mutex,可采用下面的helper来辅助完成:
/// <summary>
/// 对managed/unmanaged资源进行加锁的类
/// </summary>
public class ThreadLockHelper
{
static ThreadLockHelper mInstance = null;
Mutex mMutex = null;
private ThreadLockHelper() { }
public static ThreadLockHelper GetInstance()
{
if (mInstance == null)
{
mInstance = new ThreadLockHelper();
mInstance.mMutex = new Mutex(false, "ThreadLock");
} return (mInstance);
}
public bool CreateLock()
{
if (mMutex == null)
{ mMutex = new Mutex(false, "ThreadLock"); }
return (mMutex.WaitOne());
}
public void ReleaseLock()
{
mMutex.ReleaseMutex();
}
}
ThreadLockHelper.GetInstance().CreateLock();
objTask.DoTask();
ThreadLockHelper.GetInstance().ReleaseLock()
4、静态方法属性[MethodImpl(MethodImplOptions.Synchronized)]
指定同时只能由一个线程执行该方法。
静态方法锁定类型,而实例方法锁定实例。在任何实例函数中只能有一个线程执行,并且在任何类的静态函数中只能有一个线程执行。
有关Synchronized的应用,可详细参考:http://www.cnblogs.com/artech/archive/2008/10/17/1313209.html
5、Monitor
锁定和保护对象可以采用 lock(expression) statement_block expression:保护一个类的实例,一般地可使用this,保护一个静态变量属性(如互斥代码段在一个静态方法内部),一般使用类名就可以
当多线程公用一个对象时,也会出现和公用代码类似的问题而多线程过程中不能采用Lock,我们可以采用Monitor锁,只有得到锁的线程才可以操作
Queue oQueue=new Queue();
Monitor.Enter(oQueue);//oQueue对象只能被当前线程操纵,其他对象BLOCK
try
{
}
catch(){}
finally
{
Monitor.Exit(oQueue);//释放锁,其他线程可以操作
}
Monitor锁定后,内存存在预备队列、等待队列
Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列,锁被释放时,在预备队列中的线程可以立即获得对象锁
Monitor.Wait(); //释放对象上的锁并阻止当前线程,直到它重新获取该锁
以下为典型的生产者和消费者示例:(生产者写入一个,消费者读取一个,万变不离其宗,了解冲突解决办法即可):
class Program
{
static void Main(string[] args)
{
int result = 0; //一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
Cell cell = new Cell();
//下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
CellProd prod = new CellProd(cell, 20);
CellCons cons = new CellCons(cell, 20);
Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
//生产者线程和消费者线程都已经被创建,但是没有开始执行
try
{
producer.Start();
consumer.Start();
producer.Join();
consumer.Join();
Console.ReadLine();
}
catch (ThreadStateException e)
{
//当线程因为所处状态的原因而不能执行被请求的操作
Console.WriteLine(e);
result = 1;
}
catch (ThreadInterruptedException e)
{
//当线程在等待状态的时候中止
Console.WriteLine(e);
result = 1;
}
//尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
Environment.ExitCode = result;
}
}
public class Cell
{
int cellContents; // Cell对象里边的内容
bool readerFlag = false; // 状态标志,为true时可以读取,为false则正在写入
public int ReadFromCell()
{
lock (this) // Lock关键字保证了什么,请大家看前面对lock的介绍
{
if (!readerFlag)//如果现在不可读取
{
try
{
//等待WriteToCell方法中调用Monitor.Pulse()方法
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consume: {0}", cellContents);
readerFlag = false;
//重置readerFlag标志,表示消费行为已经完成
Monitor.Pulse(this);
//通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
}
return cellContents;
}
public void WriteToCell(int n)
{
lock (this)
{
if (readerFlag)
{
try
{
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
//当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
//当线程在等待状态的时候中止
Console.WriteLine(e);
}
}
cellContents = n;
Console.WriteLine("Produce: {0}", cellContents);
readerFlag = true;
Monitor.Pulse(this);
//通知另外一个线程中正在等待的ReadFromCell()方法
}
}
}
public class CellProd
{
Cell cell; // 被操作的Cell对象
int quantity = 1; // 生产者生产次数,初始化为1
public CellProd(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun()
{
for (int looper = 1; looper <= quantity; looper++)
cell.WriteToCell(looper); //生产者向操作对象写入信息
}
}
public class CellCons
{
Cell cell;
int quantity = 1;
public CellCons(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun()
{
int valReturned;
for (int looper = 1; looper <= quantity; looper++)
valReturned = cell.ReadFromCell();//消费者从操作对象中读取信息
}
}
6、Thread相关
(1)IsBackground:true主线程结束,线程就自动结束,false主线程等待线程结束后才会结束,如果是Windows服务中启用该选项,
或许你能看到有些事务未处理完时,APP无法停止
7、 其他
(1)5天不再惧怕多线程:http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2395279.html
(2)NET多线程探索-NET线程基础知识点:http://www.cnblogs.com/hailan2012/archive/2012/03/19/2405161.html
NET多线程探索-线程同步和通信:http://www.cnblogs.com/hailan2012/archive/2012/03/20/2408179.html
NET多线程探索-互斥锁,信号量,事件:http://www.cnblogs.com/hailan2012/archive/2012/03/22/2411934.html
(3)多线程读写锁:http://www.mysjtu.com/page/M0/S540/540055.html
(4)死锁:http://www.925lt.com/forum.php?mod=viewthread&tid=103
http://www.cnblogs.com/ols/articles/1179867.html
870

被折叠的 条评论
为什么被折叠?



