使用线程锁的体会:
多线程的使用已经很普及,但是在使用多线程时,一定要注意安全,比如:多个线程能够同时访问同一个变量时,要注意这个共同变量的资源分配策略。
通常情况下是为其【加锁】,加锁也即创建临界区,也就是当一个线程访问一段代码时,将这段代码进行锁定。但,实际理解起来并不是很简单,创建临界区在C#中使用lock关键字来锁定一段代码。
如:lock(object){操作....实际要控制并发线程要访问的公共变量}
要想真正实现代码段加锁,如上代码的object对象很重要,对于object的定义规则:
- object必须是引用类型,但还不能是string,因为系统给string的分配比较特殊;
- 其次,这个object对象必须是 多个线程同时能够识别或可见的,这样才能起到作用;
- 还有,object的定义最好是private私有的,如果是public型,别的代码对其访问时,同时某个线程正在锁定,就会造成等待;
- 最后,这个object其实是一个辅助变量,唯一的意义就是lock(object),对于object不要用作他用。
其原理很简单,就是lock住多个线程共同识别的对象,当其他线程访问某段代码段时发现这个object被锁定,就必须排队等待,直到这个object被释放,真正起到锁定代码段的关键是这个object对象,不是其他。
如果定义了多个临界区,但是这些临界区使用的是同一个【锁定实例对象】也即上述的object,那么当某线程访问一个临界区时,其他临界区也将被锁定,不能被其他线程访问。
注意:
临界区内本身就是为多个线程同时访问的,因此请不要在临界区内加入UI线程的东西,如控件的使用,否则可能造成死锁。
例子:
private StringBuilder _cache = new StringBuilder();//缓存,多线程争夺访问的资源
private object _objectLock = new object();//锁对象
void Enqueue(string val)//入队
{
#region 代码段1
lock (_objectLock)
{
_cache.Append(val);
}
#endregion
}
string Dequeue()//出队
{
#region 代码段2
lock (_objectLock)
{
int idx = _cache.ToString().IndexOf('\n');
if (idx >= 0)
{
string sub = _cache.ToString().Substring(0, idx + 1);
_cache.Remove(0, idx + 1);
return sub;
}
return null;
}
#endregion
}
以上代码有两个临界区,代码段1和代码段2,当一个线程访问代码段1时,肯定代码段1被锁定,其他线程是不允许访问代码段1的,同时其他线程也是不能访问代码段2的,因为两个代码段使用的是一个锁对象_objectLock