多线程技术(五)线程的同步
线程的同步
使用线程的一个重要方面是同步访问多个线程访问的任何变量。
背景:当多个线程共享数据,其中一个或多个线程要修改数据时,有可能引起数据不统一等问题。
同步:是指在某一时刻只有一个线程可以访问某共享数据
1、同步的含义
同步问题的产生,主要是由于在高级语言的源代码中,大多数情况下看起来是一条语句,但在最后编译好的汇编语言机器码中则会被翻译为许多条语句,从而在操作系统
调度时被划分到不同的时间片中。
例如:message += "Hello world!";
这条语句在C#语法上是一条语句,但在执行代码时,实际上它涉及到许多操作。需要重新分配内存以存储更长的新字符串,需要设置变量message使之指向新的内存,需要复制实际文本等。
2、在C#中处理同步
通过对指定对象的加锁和解锁可以实现同步代码段的访问。
在.NET的System.Threading命名空间中提供了Monitor类来实现加锁与解锁。该类中的方法都是静态的。如下表:
C#中 lock关键字提供了与Monitoy.Enter和Monitoy.Exit同样的功能,这种方法用在你的代码段不能被其他独立的线程中断的情况。通过对Monitor类的简易封装,lock为同步访问变量提供了一个非常简单的方式,其用法如下:
lock(x)
{
// 使用x的语句
}
lock语句把变量放在圆括号中,以包装对象,称为独占锁或排它锁。当执行带有lock关键字的复合语句时,独占锁会保留下来。当变量被包装在独占锁中时,其他线程就不能访问该变量。如果在上面的代码中使用独占锁,在执行复合语句时,这个线程就会失去其时间片。如果下一个获得时间片的线程试图访问变量,就会被拒绝。
Windows会让其他线程处于睡眠状态,直到解除了独占锁为止。
【例9.2】使用lock同步线程。本示例建立了10个线程
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace TestThreadLock { class Account { int balance; Random r = new Random(); public Account(int initial) { balance = initial; } //取钱 int Withdraw(int amount) { if (balance < 0) throw new Exception("余额为负!"); lock (this) { if (balance >= amount) { Console.WriteLine("原有余额:" + balance); Console.WriteLine("支取金额:" + amount); balance = balance - amount; Console.WriteLine("现有余额:" + balance); return amount; } else { return 0; //拒绝交易 } } } public void DoTransactions() { //支取随机的金额100次 for (int i = 0; i < 100; i++) Withdraw(r.Next(1, 100)); } } class TestApp { static void Main(string[] args) { Thread[] threads = new Thread[10]; Account acc = new Account(1000); for (int i = 0; i < 10; i++) { Thread t = new Thread(new ThreadStart(acc.DoTransactions)); threads[i] = t; } for (int i = 0; i < 10; i++) { threads[i].Start(); } Console.ReadKey(); } } }
同步时要注意的问题
线程同步非常重要,但只在需要时使用也是非常重要的。因为这会降低性能。原因有两个: