C#线程同步(转)
线程同步
在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。
"如果觉得有用,请帮顶! 如果有不足之处,欢迎拍砖!"
线程同步的方式
线程同步有:临界区、互斥区、事件、信号量四种方式 临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别 1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。 2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享 3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目 4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
C#中常见线程同步方法
我们介绍几种常用的C#进行线程同步的方式,这些方式可以根据其原理,找到对应上面的四种类型之一。
1、Interlocked 为多个线程共享的变量提供原子操作。
根据经验,那些需要在多线程情况下被保护的资源通常是整型值,且这些整型值在多线程下最常见的操作就是递增、递减或相加操作。Interlocked类提供了一个专门的机制用于完成这些特定的操作。这个类提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。此类的方法可以防止可能在下列情况发生的错误:计划程序在某个线程正在更新可由其他线程访问的变量时切换上下文;或者当两个线程在不同的处理器上并发执行时。 此类的成员不引发异常。
Increment 和 Decrement 方法递增或递减变量并将结果值存储在单个操作中。 在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
1)将实例变量中的值加载到寄存器中。 2)增加或减少该值。 3)在实例变量中存储该值。 如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。 然后由另一个线程执行所有三个步骤。 当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。
Exchange 方法自动交换指定变量的值。 CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。 比较和交换操作按原子操作执行。
案例分析:共享打印机。
通常我们会使用共享打印机,几台计算机共享一台打印机,每台计算机可以发出打印指令,可能会出现并发情况。当然我们知道,打印机采用了队列技术。为了简化操作,我们假定,在打印机收到命令时,即可打印,而且在同一时间只能有一个打印任务在执行。我们使用Interlocked方法来实现多线程同步。具体代码如下:
using System;
using System.Threading; namespace MutiThreadSample.ThreadSynchronization
{
class PrinterWithInterlockTest
{
/// <summary>
/// 正在使用的打印机
/// 0代表未使用,1代表正在使用
/// </summary>
public static int UsingPrinter = 0;
/// <summary>
/// 计算机数量
/// </summary>
public static readonly int ComputerCount = 3;
/// <summary>
/// 测试
/// </summary>
public static void TestPrint()
{
Thread thread;
Random random = new Random();
for (int i = 0; i < ComputerCount; i++)
{
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}",i);
Thread.Sleep(random.Next(3));
thread.Start();
}
}
/// <summary>
/// 线程执行操作
/// </summary>
private static void MyThreadProc()
{
//使用打印机进行打印
UsePrinter();
//当前线程等待1秒
Thread.Sleep(1000);
}
/// <summary>
/// 使用打印机进行打印
/// </summary>
private static bool UsePrinter()
{
//检查大引进是否在使用,如果原始值为0,则为未使用,可以进行打印,否则不能打印,继续等待
if (0 == Interlocked.Exchange(ref UsingPrinter, 1))
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //Code to access a resource that is not thread safe would go here. //Simulate some work
Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); //释放打印机
Interlocked.Exchange(ref UsingPrinter, 0);
return true;
}
else
{
Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name);
return false;
}
} }
}
2、lock 关键字
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
public void Function()
{
System.Object locker= new System.Object();
lock(locker)
{
// Access thread-sensitive resources.
}
}
lock 调用块开始位置的 Enter 和块结束位置的 Exit。
提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例,例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。
常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
1)如果实例可以被公共访问,将出现 lock (this) 问题。 2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。 3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。 最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。关于锁的研究,大家可以参考:
http://www.cnblogs.com/yank/archive/2008/10/28/1321119.html
案例分析:继续使用共享打印机的案例
我们只需对前面的例子稍作修改即可实现lock进行同步。
声明锁对象:
/// <summary>
/// 正在使用的打印机
/// </summary>
private static object UsingPrinterLocker = new object();
将打印方法修改如下:
/// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinter()
{
//临界区
lock (UsingPrinterLocker)
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
}
3、监视器
与 lock 关键字类似,监视器防止多个线程同时执行代码块。Enter 方法允许一个且仅一个线程继续执行后面的语句;其他所有线程都将被阻止,直到执行语句的线程调用 Exit。这与使用 lock 关键字一样。事实上,lock 关键字就是用 Monitor 类来实现的。例如:(继续修改共享打印机案例,增加方法UsePrinterWithMonitor)
/// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinterWithMonitor()
{
System.Threading.Monitor.Enter(UsingPrinterLocker);
try
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
finally
{
System.Threading.Monitor.Exit(UsingPrinterLocker);
}
}
使用 lock 关键字通常比直接使用 Monitor 类更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 关键字来实现的,无论是否引发异常它都执行关联的代码块。
4、同步事件和等待句柄
使用锁或监视器对于防止同时执行区分线程的代码块很有用,但是这些构造不允许一个线程向另一个线程传达事件。这需要“同步事件”,它是有两个状态(终止和非终止)的对象,可以用来激活和挂起线程。让线程等待非终止的同步事件可以将线程挂起,将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件,则线程将继续执行,而不会延迟。
同步事件有两种:AutoResetEvent 和 ManualResetEvent。它们之间唯一的不同在于,无论何时,只要 AutoResetEvent 激活线程,它的状态将自动从终止变为非终止。相反,ManualResetEvent 允许它的终止状态激活任意多个线程,只有当它的 Reset 方法被调用时才还原到非终止状态。
等待句柄,可以通过调用一种等待方法,如 WaitOne、WaitAny 或 WaitAll,让线程等待事件。System.Threading.WaitHandle.WaitOne 使线程一直等待,直到单个事件变为终止状态;System.Threading.WaitHandle.WaitAny 阻止线程,直到一个或多个指示的事件变为终止状态;System.Threading.WaitHandle.WaitAll 阻止线程,直到所有指示的事件都变为终止状态。当调用事件的 Set 方法时,事件将变为终止状态。
AutoResetEvent 允许线程通过发信号互相通信。 通常,当线程需要独占访问资源时使用该类。线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。如果当 AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。 AutoResetEvent 将立即释放线程并返回到非终止状态。 可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态:如果初始状态为终止状态,则为 true;否则为 false。 AutoResetEvent 也可以同 staticWaitAll 和 WaitAny 方法一起使用。 案例:
案例介绍:
今天我们来做饭,做饭呢,需要一菜、一粥。今天我们吃鱼。
熬粥和做鱼,是比较复杂的工作流程, 做粥:选材、淘米、熬制 做鱼:洗鱼、切鱼、腌制、烹调 为了提高效率,我们用两个线程来准备这顿饭,但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调。
来看实例代码:
using System;
using System.Threading; namespace MutiThreadSample.ThreadSynchronization
{
/// <summary>
/// 案例:做饭
/// 今天的Dinner准备吃鱼,还要熬粥
/// 熬粥和做鱼,是比较复杂的工作流程,
/// 做粥:选材、淘米、熬制
/// 做鱼:洗鱼、切鱼、腌制、烹调
/// 我们用两个线程来准备这顿饭
/// 但是,现在只有一口锅,只能等一个做完之后,另一个才能进行最后的烹调
/// </summary>
class CookResetEvent
{
/// <summary>
///
/// </summary>
private AutoResetEvent resetEvent = new AutoResetEvent(false);
/// <summary>
/// 做饭
/// </summary>
public void Cook()
{
Thread porridgeThread = new Thread(new ThreadStart(Porridge));
porridgeThread.Name = "Porridge";
porridgeThread.Start(); Thread makeFishThread = new Thread(new ThreadStart(MakeFish));
makeFishThread.Name = "MakeFish";
makeFishThread.Start(); //等待5秒
Thread.Sleep(5000); resetEvent.Reset();
}
/// <summary>
/// 熬粥
/// </summary>
public void Porridge()
{
//选材
Console.WriteLine("Thread:{0},开始选材", Thread.CurrentThread.Name); //淘米
Console.WriteLine("Thread:{0},开始淘米", Thread.CurrentThread.Name); //熬制
Console.WriteLine("Thread:{0},开始熬制,需要2秒钟", Thread.CurrentThread.Name);
//需要2秒钟
Thread.Sleep(2000);
Console.WriteLine("Thread:{0},粥已经做好,锅闲了", Thread.CurrentThread.Name); resetEvent.Set();
}
/// <summary>
/// 做鱼
/// </summary>
public void MakeFish()
{
//洗鱼
Console.WriteLine("Thread:{0},开始洗鱼",Thread.CurrentThread.Name); //腌制
Console.WriteLine("Thread:{0},开始腌制", Thread.CurrentThread.Name); //等待锅空闲出来
resetEvent.WaitOne(); //烹调
Console.WriteLine("Thread:{0},终于有锅了", Thread.CurrentThread.Name);
Console.WriteLine("Thread:{0},开始做鱼,需要5秒钟", Thread.CurrentThread.Name);
Thread.Sleep(5000);
Console.WriteLine("Thread:{0},鱼做好了,好香", Thread.CurrentThread.Name); resetEvent.Set();
}
}
}
ManualResetEvent与AutoResetEvent用法基本类似,这里不多做介绍。
5、Mutex对象
mutex 与监视器类似;它防止多个线程在某一时间同时执行某个代码块。事实上,名称“mutex”是术语“互相排斥 (mutually exclusive)”的简写形式。然而与监视器不同的是,mutex 可以用来使跨进程的线程同步。mutex 由 Mutex 类表示。当用于进程间同步时,mutex 称为“命名 mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个 mutex 对象。 尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
本地 mutex 和系统 mutex Mutex 分两种类型:本地 mutex 和命名系统 mutex。 如果使用接受名称的构造函数创建了 Mutex 对象,那么该对象将与具有该名称的操作系统对象相关联。 命名的系统 mutex 在整个操作系统中都可见,并且可用于同步进程活动。 您可以创建多个 Mutex 对象来表示同一命名系统 mutex,而且您可以使用 OpenExisting 方法打开现有的命名系统 mutex。 本地 mutex 仅存在于进程当中。 进程中引用本地 Mutex 对象的任意线程都可以使用本地 mutex。 每个 Mutex 对象都是一个单独的本地 mutex。
在本地Mutex中,用法与Monitor基本一致
继续修改前面的打印机案例:
声明Mutex对象:
/// <summary>
/// mutex对象
/// </summary>
private static Mutex mutex = new Mutex();
具体操作:
/// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinterWithMutex()
{
mutex.WaitOne();
try
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
finally
{
mutex.ReleaseMutex();
}
}
多线程调用:
/// <summary>
/// 测试
/// </summary>
public static void TestPrint()
{
Thread thread;
Random random = new Random();
for (int i = 0; i < ComputerCount; i++)
{
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}", i);
Thread.Sleep(random.Next(3));
thread.Start();
}
}
/// <summary>
/// 线程执行操作
/// </summary>
private static void MyThreadProc()
{
//使用打印机进行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//当前线程等待1秒
Thread.Sleep(1000);
}
最后的打印机案例代码:
using System;
using System.Threading; namespace MutiThreadSample.ThreadSynchronization
{
class PrinterWithLockTest
{
/// <summary>
/// 正在使用的打印机
/// </summary>
private static object UsingPrinterLocker = new object();
/// <summary>
/// 计算机数量
/// </summary>
public static readonly int ComputerCount = 3;
/// <summary>
/// mutex对象
/// </summary>
private static Mutex mutex = new Mutex();
/// <summary>
/// 测试
/// </summary>
public static void TestPrint()
{
Thread thread;
Random random = new Random();
for (int i = 0; i < ComputerCount; i++)
{
thread = new Thread(MyThreadProc);
thread.Name = string.Format("Thread{0}", i);
Thread.Sleep(random.Next(3));
thread.Start();
}
}
/// <summary>
/// 线程执行操作
/// </summary>
private static void MyThreadProc()
{
//使用打印机进行打印
//UsePrinter();
//monitor同步
//UsePrinterWithMonitor();
//用Mutex同步
UsePrinterWithMutex();
//当前线程等待1秒
Thread.Sleep(1000);
}
/// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinter()
{
//临界区
lock (UsingPrinterLocker)
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
} /// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinterWithMonitor()
{
System.Threading.Monitor.Enter(UsingPrinterLocker);
try
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
finally
{
System.Threading.Monitor.Exit(UsingPrinterLocker);
}
} /// <summary>
/// 使用打印机进行打印
/// </summary>
private static void UsePrinterWithMutex()
{
mutex.WaitOne();
try
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//模拟打印操作
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
}
finally
{
mutex.ReleaseMutex();
}
}
}
}
6、读取器/编写器锁
ReaderWriterLockSlim 类允许多个线程同时读取一个资源,但在向该资源写入时要求线程等待以获得独占锁。
可以在应用程序中使用 ReaderWriterLockSlim,以便在访问一个共享资源的线程之间提供协调同步。 获得的锁是针对 ReaderWriterLockSlim 本身的。 设计您应用程序的结构,让读取和写入操作的时间尽可能最短。 因为写入锁是排他的,所以长时间的写入操作会直接影响吞吐量。 长时间的读取操作会阻止处于等待状态的编写器,并且,如果至少有一个线程在等待写入访问,则请求读取访问的线程也将被阻止。
案例:构造一个线程安全的缓存
using System;
using System.Threading;
using System.Collections.Generic; namespace MutiThreadSample.ThreadSynchronization
{
/// <summary>
/// 同步Cache
/// </summary>
public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
/// <summary>
/// 读取
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string Read(int key)
{
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
/// <summary>
/// 添加项
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(int key, string value)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
/// <summary>
/// 添加项,有超时限制
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public bool AddWithTimeout(int key, string value, int timeout)
{
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// 添加或者更新
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
/// <summary>
/// 删除项
/// </summary>
/// <param name="key"></param>
public void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
/// <summary>
///
/// </summary>
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
}
}
7、Semaphore 和 SemaphoreSlim
System.Threading.Semaphore 类表示一个命名(系统范围)信号量或本地信号量。 它是一个对 Win32 信号量对象的精简包装。 Win32 信号量是计数信号量,可用于控制对资源池的访问。 SemaphoreSlim 类表示一个轻量的快速信号量,可用于在一个预计等待时间会非常短的进程内进行等待。 SemaphoreSlim 会尽可能多地依赖由公共语言运行时 (CLR) 提供的同步基元。 但是,它也会根据需要提供延迟初始化的、基于内核的等待句柄,以支持等待多个信号量。 SemaphoreSlim 还支持使用取消标记,但它不支持命名信号量或使用等待句柄来进行同步。
线程通过调用 WaitOne 方法来进入信号量,此方法是从 WaitHandle 类派生的。 当调用返回时,信号量的计数将减少。 当一个线程请求项而计数为零时,该线程会被阻止。 当线程通过调用 Release 方法释放信号量时,将允许被阻止的线程进入。 并不保证被阻塞的线程进入信号量的顺序,例如先进先出 (FIFO) 或后进先出 (LIFO)。信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。
案例分析:购买火车票
还得排队进行购买,购买窗口是有限的,只有窗口空闲时才能购买
8、障碍(Barrier)4.0后技术
使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。 通过在一系列阶段间移动来协作完成一组任务,此时该组中的每个任务发信号指出它已经到达指定阶段的 Barrier 并且暗中等待其他任务到达。 相同的 Barrier 可用于多个阶段。
9、SpinLock(4.0后) SpinLock结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转。 在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock 的性能将高于其他类型的锁。 不过,我们建议您仅在通过分析确定 System.Threading.Monitor 方法或 Interlocked 方法显著降低了程序的性能时使用 SpinLock。 即使 SpinLock 未获取锁,它也会产生线程的时间片。 它这样做是为了避免线程优先级别反转,并使垃圾回收器能够继续执行。 在使用 SpinLock 时,请确保任何线程持有锁的时间不会超过一个非常短的时间段,并确保任何线程在持有锁时不会阻塞。 由于 SpinLock 是一个值类型,因此,如果您希望两个副本都引用同一个锁,则必须通过引用显式传递该锁。
10、SpinWait(4.0后)
System.Threading.SpinWait 是一个轻量同步类型,可以在低级别方案中使用它来避免内核事件所需的高开销的上下文切换和内核转换。 在多核计算机上,当预计资源不会保留很长一段时间时,如果让等待线程以用户模式旋转数十或数百个周期,然后重新尝试获取资源,则效率会更高。 如果在旋转后资源变为可用的,则可以节省数千个周期。 如果资源仍然不可用,则只花费了少量周期,并且仍然可以进行基于内核的等待。 这一旋转-等待的组合有时称为“两阶段等待操作”。
下面的基本示例采用微软案例:无锁堆栈
总结:
尽管有这么多的技术,但是不同的技术对应不同的场景,我们必须熟悉其特点和适用范围。在应用时,必须具体问题具体分析,选择最佳的同步方式
C#线程同步(转)的更多相关文章
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- C#多线程之线程同步篇3
在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...
- C#多线程之线程同步篇2
在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...
- C#多线程之线程同步篇1
在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...
- C# 线程同步的三类情景
C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform ...
- Java进击C#——语法之线程同步
上一章我们讲到关于C#线程方向的应用.但是笔者并没有讲到多线程中的另一个知识点--同步.多线程的应用开发都有可能发生脏数据.同步的功能或多或少都会用到.本章就要来讲一下关于线程同步的问题.根据笔者这几 ...
- Java多线程 3 线程同步
在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的 ...
- JAVA之线程同步的三种方法
最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下.这三种方法分别是:synchroni ...
- 三、线程同步之Sysnchronized关键字
线程同步 问题引入 观察一面一段小程序: public class Main { private static int amount = 0; public static void main(Stri ...
- 【C#进阶系列】29 混合线程同步构造
上一章讲了基元线程同步构造,而其它的线程同步构造都是基于这些基元线程同步构造的,并且一般都合并了用户模式和内核模式构造,我们称之为混合线程同步构造. 在没有线程竞争时,混合线程提供了基于用户模式构造所 ...
随机推荐
- POJ 3299 Humidex(简单的问题)
[简要题意]:什么是温度,湿度--,之间的转换.. [分析]:式已被赋予. // 252k 0Ms /* 当中exp表示的是求e的x次幂 解法就直接依据题目中的公式解决就好!! */ #include ...
- boost进程间通信经常使用开发一篇全(消息队列,共享内存,信号)
本文概要: 敏捷开发大家想必知道并且评价甚高,缩短开发周期,提高开发质量.将大project独立为不同的小app开发,整个开发过程,程序可用可測,所以提高了总体的质量.基于这样的开发模式和开发理念,进 ...
- asp.net [AjaxMethod]
AjaxPro.2.dll cs 代码 using AjaxPro; Utility.RegisterTypeForAjax(typeof(BOMdr_KT)); [Ajax.AjaxMethod() ...
- Linq技术四:动态Linq技术 -- Linq.Expressions
前面介绍了Linq的三个方面应用:Linq to SQL, Linq to XML和Linq to Object,这篇介绍一下动态Linq的实现方式及应用场景. 命名空间: System.Linq; ...
- 如何使用Visual Studio 2013 开发PHP5.6项目
原文如何使用Visual Studio开发PHP项目 在windows下开发php除了记事本 DW 以及一帮Zend studio,Eclipse,NetBeans之流以外,个人感觉还是vsiual ...
- 最新发布树莓派2代Wi-Fi自动连接实战(适合初学者)
话说天地会珠海分舵在上几天才刚给大家分享了个海外资讯说树莓派2已经发布且Windows10加盟之类的资讯,具体请查看<海外优秀资讯抢先看8 - Windows 10 for Raspberry ...
- 使用Scala操作Mongodb
介绍 Scala是一种功能性面向对象语言.它融汇了很多前所未有的特性.而同一时候又执行于JVM之上.随着开发人员对Scala的兴趣日增,以及越来越多的工具支持,无疑Scala语言将成为你手上一件不可缺 ...
- ASP.NET MVC创建的网站
ASP.NET MVC创建的网站 最近在写一个网站,昨天刚写完,由于要和朋友一起测试,但是他电脑上没有环境,所以希望我在自己电脑上部署一下,让他直接通过浏览器来访问来测试,所以从昨晚到今天上午,通 ...
- 使用jQuery热门功能实现
非常多站点上都有返回顶部的效果,本文阐述怎样使用jquery实现返回顶部button. 首先须要在顶部加入例如以下html元素: <p id="back-to-top"> ...
- PLSQL导入/导出数据方法
PLSQL导入/导出数据方法 PLSQL导入/导出数据方法 以前导数据库信息的时候,总是会先开启sql窗口,把自己手写的建表文件复制进去,然后再导入数据信息. 今天突然懒得去找以前的建表文件,而想用S ...