一、线程同步概述
在多线程程序中,当存在共享变量和抢占资源的情况时,需要使用线程同步机制来防止发生这些冲突,这样才能保证得到可预见的结果,也就是线程安全的。否则就会出现不可预知的结果产生线程不安全问题。特别是在访问同一个数据的时候最为明显。主要通过以下四个方式进行:
|
构成 |
目的 |
|
Sleep |
阻止给定的时间周期 |
|
Join |
等待另一个线程完成 |
|
构成 |
目的 |
是否跨进程 |
速度 |
|
lock |
确保只有一个线程访问某个资源或某段代码。 |
否 |
快 |
|
Mutex |
确保只有一个线程访问某个资源或某段代码。可被用于防止一个程序的多个实例同时运行。 |
是 |
中等 |
|
Semaphore |
确保不超过指定数目的线程访问某个资源或某段代码。 |
是 |
中等 |
|
构成 |
目的 |
跨进程? |
速度 |
|
EventWaitHandle |
允许线程等待直到它受到了另一个线程发出信号。 |
是 |
中等 |
|
Wait 和 Pulse* |
允许一个线程等待直到自定义阻止条件得到满足。 |
否 |
中等 |
|
构成 |
目的 |
跨进程? |
速度 |
|
Interlocked* |
完成简单的非阻止原子操作。 |
是(内存共享情况下) |
非常快 |
|
volatile* |
允许安全的非阻止在锁之外使用个别字段。 |
非常快 |
bool blocked = (Thread.ThreadState & ThreadState.WaitSleepJoin) != 0;解除阻塞发生的情况:(其中对于使用Suspend方法挂起的线程并不视为阻塞状态)
static object locker = new object();
static int val1, val2;
static void Main(string[] args)
{
val1 = 100;
val2 = 10;
Thread t1 = new Thread(Go);
Thread t2 = new Thread(Go);
t1.Start();
t2.Start();
Console.ReadKey();
}
static void Go()
{
lock (locker)
{
if (val2 != 0) Console.WriteLine(val1 / val2);
val2 = 0;
}
}Monitor.Enter(locker);
try
{
if (val2 != 0) Console.WriteLine(val1/val2);
val2 = 0;
}
finally
{
Monitor.Exit(locker);
}上述使用的locker同步对象,必须是引用类型,最好是在私有类里面定义,防止外部锁定相同对象。可使用lock(this){...}来精确控制锁的范围和粒度,同时,锁没有阻止对同步对象本身的访问。 static void Main(string[] args)
{
using (var mutex = new Mutex(false, "mutex"))
{
if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false))
{
Console.WriteLine("Another app instance is running. Bye!");
return;
}
RunProgram();
}
}
static void RunProgram()
{
Console.WriteLine("Running. Press Enter to exit");
Console.ReadLine();
}5、Semaphore static SemaphoreSlim sem = new SemaphoreSlim(3);
static void Main(string[] args)
{
for (int i = 1; i < 10; i++)
new Thread(Enter).Start(i);
Console.ReadKey();
}
static void Enter(object id)
{
Console.WriteLine(id + "wants to enter");
sem.Wait();
Console.WriteLine(id + "enter in!");
Thread.Sleep(1000 * (int)id);
Console.WriteLine(id + "is leaving");
sem.Release();
} static SemaphoreSlim sem = new SemaphoreSlim(3);
static void Main(string[] args)
{
Thread t = new Thread(delegate()
{
try
{
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Exception!");
}
Console.WriteLine("Woken");
});
t.Start();
t.Interrupt();
Console.ReadKey();
}WaitOne 接受一个可选的超时参数——当等待以超时结束时这个方法将返回false,WaitOne在等待整段时间里也通知离开当前的同步内容,为了避免过多的阻止发生。Reset作用是关闭旋转门,也就是无论此时是否已经set过,都将阻塞下一次WaitOne——它应该是开着的。
使用构造函数创建对象或者使用基类创建:
EventWaitHandle wh = new AutoResetEvent (false); EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto);2、跨进程的EventWaitHandle
EventWaitHandle的构造器允许以“命名”的方式进行创建,它有能力跨多个进程。名称是个简单的字符串,可能会无意地与别的冲突!如果名字使用了,你将引用相同潜在的EventWaitHandle,除非操作系统创建一个新的。
EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto, "MyCompany.MyApp.SomeName");如果两个程序都运行上述代码,他们就可以彼此发送信号,等待句柄可以跨越两个进程中的所有线程。
假设希望在后台完成任务,但又不在每次得到任务时再创建一个新的线程。可以通过一个轮询的线程来完成:等待一个任务,执行它,然后等待下一个任务。这是一个普遍的多线程方案。也就是在创建线程上切分内务操作,任务执行被序列化,在多个工作线程和过多的资源消耗间排除潜在的不想要的操作。
static EventWaitHandle ready = new AutoResetEvent(false);
static EventWaitHandle go = new AutoResetEvent(false);
static volatile string task;
static void Main(string[] args)
{
new Thread(work).Start();
for (int i = 1; i <= 5; i++)
{
ready.WaitOne();
task = "#".PadRight(i, ‘@‘);
go.Set();
}
ready.WaitOne();
task = null;
go.Set();
Console.ReadKey();
}
static void work()
{
while (true)
{
ready.Set();
go.WaitOne();
if (task == null) return;
Console.WriteLine(task);
}
}4、生成者消费者模型
还有一个普遍的线程方案是在后台工作进程从队列中分配任务,称为生产者/消费者队列:在工作线程中生产者入列任务,消费者出列任务。下面例子中,AutoResetEvent用来通知工作线程,只有在用完任务事等待。集合类队列来表示,通过锁来控制访问确保线程安全,队列为Null时结束任务。
class ProducerConsumerQueue : IDisposable
{
EventWaitHandle wh = new AutoResetEvent(false);
Thread worker;
object locker = new object();
Queue<string> tasks = new Queue<string>();
public ProducerConsumerQueue()
{
worker = new Thread(Work);
worker.Start();
}
public void EnqueueTask(string task)
{
lock (locker) tasks.Enqueue(task);
wh.Set();
}
public void Dispose()
{
EnqueueTask(null); // Signal the consumer to exit.
worker.Join(); // Wait for the consumer‘s thread to finish.
wh.Close(); // Release any OS resources.
}
void Work()
{
while (true)
{
string task = null;
lock (locker)
if (tasks.Count > 0)
{
task = tasks.Dequeue();
if (task == null) return;
}
if (task != null)
{
Console.WriteLine("Performing task: " + task);
Thread.Sleep(1000);
}
else
wh.WaitOne();
}
}
} static void Main(string[] args)
{
using (ProducerConsumerQueue q = new ProducerConsumerQueue())
{
q.EnqueueTask("Hello");
for (int i = 0; i < 10; i++) q.EnqueueTask("Say " + i);
q.EnqueueTask("Goodbye!");
}
Console.ReadKey();
}5、ManualResetEvent
ManualResetEvent是AutoResetEvent变化的一种形式,它的不同之处在于:在线程被WaitOne的调用而通过的时候,它不会自动地reset,这个过程就像大门一样——调用Set打开门,允许任何数量的已执行WaitOne的线程通过;调用Reset关闭大门,可能会引起一系列的“等待者”直到下次门打开。
ManualResetEvent有时被用于给一个完成的操作发送信号,又或者一个已初始化正准备执行工作的线程。
原文:http://blog.csdn.net/u010487568/article/details/22309551