在多个线程之间共享数据时,需要考虑线程同步问题,必须确保每次只有一个线程访问和改变共享数据。
C#中使用lock语句可以轻松地设置和解除锁定以期达到每次只有一个线程访问和改变共享数据的目的。
下面是一个多线程访问共享数据的实例,看看在没有进行同步操作的情况下会出现什么样的问题?
using System;
using System.Threading;
namespace LockExamples
{
class Program
{
static int account = 1000;//账户
static int pocket = 0;//口袋
static void Main(string[] args)
{
int threadCount = 10;
var threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = new Thread(DoWork);
threads[i].Start();
}
for (int i = 0; i < threadCount; i++)
{
threads[i].Join();
}
Console.WriteLine("pocket=" + pocket);
}
public static void DoWork()
{
if (account >= 1000)
{
Thread.Sleep(10);//自动取款机打了个小盹
account -= 1000;
pocket += 1000;
}
}
}
}
可以将示例代码理解成:一个用户分十次从自己的银行账户中取钱。
取钱的逻辑由下面的代码来实现。
if (account >= 1000)
{
Thread.Sleep(10);//自动取款机打了个小盹
account -= 1000;
pocket += 1000;
}
当账户中的余额大于等于1000时,就取出1000放进自己的口袋。
因为用户当前的账户中仅剩下1000,所以就算用户取了10次,最终口袋中也应该只有1000。那么实际情况又是怎样的呢?
请看下面的执行结果(结果也可能是1000,2000,...,9000中的一个)。
结果竟然是10000!!!!!!!!
用户从仅有1000余额的账户中取出了10000,实在是一件令人振奋人心的事。
不过对于银行来说,这可不是件什么好事,因为照这样下去,银行的钱迟早会被用户掏空。
为什么会出现这样的结果呢?
这是因为没有对多线程访问共享数据进行同步,10个线程同时进入了取钱的逻辑,所以一共取出了10000。
为了解决这个问题,可以使用lock语句来同步多线程访问共享数据。下面是增加lock语句后的取钱逻辑。
private static object o = new object();
lock (o)
{
if (account >= 1000)
{
Thread.Sleep(10);//自动取款机打了个小盹
account -= 1000;
pocket += 1000;
}
}
代码中使用lock关键字锁定对象o,当一个线程获得锁定后,其他线程就无法再获得锁定,只有当当前线程解除锁定后,其他对象才可以重新获得锁定,这样一来,就可以保证每次只有一个线程获得锁定进而访问和修改共享数据。
多次执行修改后的示例代码,每次都可以得到以下的正确结果。
lock语句锁定的对象,必须是引用类型。因为锁定值类型只是锁定了一个副本,没什么意义。