当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作。直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,这种时候很容易出现问题
争用条件
如果两个或多个线程访问相同的对象。并且对共享状态的访问没有同步,就会出现争用条件
class StateObject
{
private int state = 5;
public void ChangeState()
{
state++;
if (state == 5)
{
Console.WriteLine("value=5");
}
state = 5;
}
}
public static void ThreadSync_Lock_Example_01()
{
StateObject m = new StateObject();
Thread t1 = new Thread(ChangeState);
t1.Start(m);
Console.ReadKey();
static void ChangeState(object o)
{
StateObject m = o as StateObject;
while (true)
{
m.ChangeState();
}
}
}
此时运行程序是没有输出的,因为StateObject
类中state
初始值是5,if
条件不会进入,随后又将state
从新初始化为5
两个线程执行
public static void ThreadSync_Lock_Example_02()
{
StateObject m = new StateObject();
Thread t1 = new Thread(ChangeState);
Thread t2 = new Thread(ChangeState);
t1.Start(m);
t2.Start(m);
Console.ReadKey();
static void ChangeState(object o)
{
StateObject m = o as StateObject;
while (true)
{
m.ChangeState();
}
}
}
此时会不停的打印”value=5”,原因在于:一个线程在判断语句处时,另一个线程可能又将state
的值改为了5,而导致输出合法
死锁
class Deadlock
{
static StateObject o1 = new StateObject();
static StateObject o2 = new StateObject();
public static void DeadlockA(object o)
{
lock (o1)
{
Console.WriteLine("我是线程{0},我锁定了对象o1", o);
lock (o2)
{
Console.WriteLine("我是线程{0},我锁定了对象o2", o);
}
}
}
public static void DeadlockB(object o)
{
lock (o2)
{
Console.WriteLine("我是线程{0},我锁定了对象o2", o);
lock (o1)
{
Console.WriteLine("我是线程{0},我锁定了对象o1", o);
}
}
}
}
Thread t1 = new Thread(Deadlock.DeadlockA);
Thread t2 = new Thread(Deadlock.DeadlockB);
t1.Start("t1");
t2.Start("t2");
t1
线程执行DeadlockA()
方法顺序锁定o1
和o2
t2
线程执行DeadlockB()
方法顺序锁定o2
和o1
当前结果显示t1线程锁定了o1
后,t2
线程在t1
线程锁定o1
后抢占进来,锁定了o2
。t2
在等t1
解锁,t1
在等t2
解锁,都处于挂起状态在等对方解锁,这就形成了死锁,线程将无限等待下去
这个问题应该从一开始就设计好锁定顺序,也可以为锁定义超时时间来处理,保证“上锁”这个操作在一个线程上执行也是避免死锁的方法之一
C#中有多个用于多线程的同步技术
- lock语句
- Interlocked类
- Monitor类
- SpinLock类
- WaitHandle类
- Mutex类
- Semapphore类
- Event类
- Barrier类
- ReaderWriteLockSlim类
其中lock语句/Interlocked类/Monitor类可用于进程内存的同步,其它几个提供了多进程之间的线程同步
Lock
C#使用lock
语句锁定在线程中共享的变量,如果一个线程锁定了变量,另一个线程就必须等待该锁定的解除
static void ChangeState(object o)
{
StateObject m = o as StateObject;
while (true)
{
//给变量m加锁
lock (m)
{
m.ChangeState();
}
}
}
因为实例的对象也可以用于外部的同步访问,而且不能在类自身控制这种访问,所以应采用SyncRoot
模式,创建私有对象,将这个对象用于lock
语句
private static object syncRoot= new object();
static void ChangeState(object o)
{
StateObject m = o as StateObject;
while (true)
{
lock (aync)
{
m.ChangeState();
}
}
}
需要注意
lock
只能锁定对象,即引用类型,不能锁定值类型lock
不能锁定空值,因为null
是不需要被释放的- 不能锁定
string
类型,虽然它也是引用类型的。因为字符串类型被CLR暂留
,这意味着整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例 - 避免锁定
public
类型,如果该实例可以被公开访问,则lock(this)
可能会有问题,因为不受控制的代码也可能会锁定该对象
锁是否必须是静态类型?
如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行
如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的。只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法
class ThreadSafe
{
private static object _locker = new object();
void Go()
{
lock (_locker)
{
......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行
}
}
private object _locker2=new object();
void GoTo()
{
lock(_locker2)
//共享数据的操作,非静态方法,是用非静态锁,确保同一个实例的方法调用者排队执行
}
}
同步对象可以兼作它lock的对象
如:
class ThreadSafe
{
private List <string> _list = new List <string>();
void Test()
{
lock (_list)
{
_list.Add ("Item 1");
}
}
}
Monitors
lock其实是Monitors的简洁写法
lock (syncRoot)
{
m.ChangeState();
}
两者其实是一样的
Monitor.Enter(syncRoot);
try
{
m.ChangeState();
}
finally
{
Monitor.Exit(syncRoot);
}
Mutex
互斥锁是一个互斥的同步对象,同一时间有且仅有一个线程可以获取它。可以实现进程级别上线程的同步
public static void ThreadSync_Mutex_Example_01()
{
for (int i = 0; i < 3; i++)
{
//在不同的线程中调用受互斥锁保护的方法
Thread test = new Thread(MutexMethod);
test.Start();
}
Console.Read();
static void MutexMethod()
{
//实例化一个互斥锁
Mutex mutex = new Mutex();
Console.WriteLine("{0} 请求获取互斥锁", Thread.CurrentThread.ManagedThreadId);
mutex.WaitOne();
Console.WriteLine("{0} 已获取到互斥锁", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("{0} 准备释放互斥锁", Thread.CurrentThread.ManagedThreadId);
// 释放互斥锁
mutex.ReleaseMutex();
Console.WriteLine("{0} 已经释放互斥锁", Thread.CurrentThread.ManagedThreadId);
}
}
互斥锁的带有三个参数的构造函数
initiallyOwned
: 如果initiallyOwned
为true
,互斥锁的初始状态就是被所实例化的线程所获取,否则实例化的线程处于未获取状态name
:该互斥锁的名字,在操作系统中只有一个命名为name
的互斥锁mutex
,如果一个线程得到这个name
的互斥锁,其他线程就无法得到这个互斥锁了,必须等待那个线程对这个线程释放createNew
:如果指定名称的互斥体已经存在就返回false
,否则返回true