在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
使用线程时最头痛的就是共享资源的同步问题,处理不好会得到错误的结果,C#处理共享资源有以下几种: 1、lock锁 需要注意的地方: 1).lock不能锁定空值某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null) 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中
的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。 ,值类型不是引用类型的 5).lock就避免锁定public 类型或不受程序控制的对象。
应用场景:经常会应用于防止多线程操作导致公用变量值出现不确定的异常,用于确保操作的安全性
2、 互斥锁(Mutex) 互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它。 互斥锁可适用于一个共享资源每次只能被一个线程访问的情况 我们可以把Mutex看作一个出租车,乘客看作线程。乘客首先等车,然后上车,最后下车。当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而 线程与C# Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待C# Mutex对象被释放,如果它等待的C# Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个 C# Mutex对象的线程都只有等待。 如果要获取一个互斥锁。应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类 它处于等到状态直至所调用互斥锁可以被获取,因此该方法将组织住主调线程直到指定的互斥锁可用,如果不需要拥有互斥锁,用ReleaseMutex方法释放,从而使互斥锁可以被另外一个线程所获取.
3、semaphore 其中lock 和mutex 差不多,都是锁定同一个资源,不同之处mutex在整个进程中都可以访问到。 而semaphore是锁定多个资源,比如同一时期只能有两个线程访问,其它线程只能等待其中之一释放锁才能使用,Semaphore就是一个可以多次进入的“Mutex”。Mutex永远只允许一个线程拥有它,而Semaphore可以允许多个线程请求,因此Semaphore被用于管理一次可以允许多个线程进入并发访问资源的情况。 下面是一个简单的例子,: class Program { static Semaphore sp = new Semaphore(2,2); static void Main(string[] args) { DoWork(); Console.Read(); } private static void DoWork() { for (int i = 0; i < 10; i++) { Task.Run(() => { sp.WaitOne(); Console.WriteLine("线程:"+Thread.CurrentThread.ManagedThreadId+",开始运行"); Thread.Sleep(new Random().Next(1000)); Console.WriteLine("线程:" + Thread.CurrentThread.ManagedThreadId + ",结束此运行"); sp.Release(); }); } } } 另举一个复杂一些的例子:学生都去图书馆查资料,图书馆共有3台电脑,如果来的人超过3人则需要排队等待,此例子中还要注意一点,那个学生选择那台电脑,学生找空闲电脑用Mutex锁定电脑对象,否则定位的电脑可能是错误的(可能会出现多名同学使用同一台电脑的情况,使用mutex锁定资源,这样才能确保一台空闲电脑只能是一名学生选择) class Program { //图书馆拥有的公用计算机 private const int ComputerNum = 3; private static Computer[] LibraryComputers; //同步信号量 public static Semaphore sp = new Semaphore(ComputerNum, ComputerNum); static void Main(string[] args) { //图书馆拥有ComputerNum台电脑 LibraryComputers = new Computer[ComputerNum]; for (int i = 0; i < ComputerNum; i++) LibraryComputers[i] = new Computer("Computer" + (i + 1).ToString()); int peopleNum = 0; Random ran = new Random(); Thread user; System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……", ComputerNum); //每次创建若干个线程,模拟人排队使用计算机 while (System.Console.ReadKey().Key != ConsoleKey.Escape) { peopleNum = ran.Next(0, 10); System.Console.WriteLine("\n有{0}人在等待使用计算机。", peopleNum); Task[] ts = new Task[peopleNum]; for (int i = 0; i <peopleNum; i++) { int n = i+1; ts[i]=Task.Run(() => { UseComputer("User" + n.ToString()); }); } Task.WaitAll(ts); Console.WriteLine("All threads finished!"); } } static Mutex m = new Mutex(); //线程函数 static void UseComputer(Object UserName) { sp.WaitOne();//等待计算机可用 //查找可用的计算机 Computer cp = null; m.WaitOne(); for (int i = 0; i < ComputerNum; i++) { if (LibraryComputers[i].IsOccupied == false) { LibraryComputers[i].IsOccupied = true; cp = LibraryComputers[i]; break; } } m.ReleaseMutex(); //使用计算机工作 cp.Use(UserName.ToString()); //不再使用计算机,让出来给其他人使用 sp.Release(); } } class Computer { public readonly string ComputerName = ""; public Computer(string Name) { ComputerName = Name; } //是否被占用 public bool IsOccupied = false; //人在使用计算机 public void Use(String userName) { System.Console.WriteLine("{0}开始使用计算机{1}", userName, ComputerName); Thread.Sleep(new Random().Next(2000, 6000)); //随机休眠,以模拟人使用计算机 System.Console.WriteLine("{0}结束使用计算机{1}", userName, ComputerName); IsOccupied = false; } } 4. AutoResetEvent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。如果 AutoResetEvent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程 调用 Set 向 AutoResetEvent 发信号以释放等待线程。AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。 可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。 举例:面试时,每次叫一个,只能一个人进去。 class Program { static AutoResetEvent are = new AutoResetEvent(false); static void Main(string[] args) { //10个人排队,叫一声,进一个 for (int i = 0; i < 10; i++) { Task.Run(() => { are.WaitOne(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId+"进门"); Thread.Sleep(new Random().Next(1000)); Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "出门"); }); } System.Console.WriteLine("G:放行,ESC:退出\n"); Action action = new Action(() => { are.Set(); }) ; while (true) { ConsoleKey key = Console.ReadKey(true).Key; if (key == ConsoleKey.G) action(); if (key == ConsoleKey.Escape) { break; } } } } 5、ManualResetEvent ManualResetEvent就像一个信号灯,可以利用它的信号,控制当前线程是挂起状态还是运行状态。 举例://模拟3辆汽车过红绿灯 class Program { static ManualResetEvent mre = new ManualResetEvent(false); static void Main(string[] args) { //模拟3辆汽车过红绿灯 for (int i = 0; i < 3; i++) { Task.Run(() => { int count = 0; while (true) { mre.WaitOne(); Thread.Sleep(new Random().Next(5000)); count++; Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "第{0}次开始运行", count); } }); } Action stop = delegate() { mre.Reset(); Console.WriteLine("红灯"); }; Action go = delegate() { mre.Set(); Console.WriteLine("绿灯"); }; System.Console.WriteLine("G:绿灯,R:红灯\n"); while (true) { var k = Console.ReadKey(true).Key; if (k == ConsoleKey.G) { go(); } else if (k == ConsoleKey.R) { stop(); } else { System.Console.WriteLine("G:绿灯,R:红灯\n"); } } } }
|
请发表评论