而是在托管堆上,可能被其他线程更改。
但只使用局部变量的函数在.NET类库中是很少的,但.NET类库中还是有那么多线程安全的函数,是为什么呢?
因为,即使一个函数使用了共享资源,如果其所使用的共享资源都是线程安全的,则这个函数也是线程安全的。
比如说这样的函数:
private const string connectionString = “…”;
public string GetConnectionString()
{
return connectionString;
}
虽然这个函数使用了一个共享资源connectionString,但因为这个资源是线程安全的,所以这个函数还是线程安全的。
同样的,我们可以得出,如果一个函数只调用线程安全的函数,只使用线程安全的共享资源,那么这个函数也是线程安全的。
这里有一个容易被忽略的问题,运算符。并不是所有的运算符(尤其是重载后的运算符)都是线程安全的。
四、互斥锁
有时候我们不得不面对线程不安全的问题,比如说在一开始提出来的那个例子,多线程完成100次任务,我们怎样才能解决这个问题,一个简单的办法就是给共享资源加上互斥锁。在C#中这很简单。比如一开始的那个例子:
{public static int count = 0;//全局计数器
}
//…
void ThreadMethod()//运行在每个线程的方法
{
while( true )
{
lock ( typeof( Environment ) )
{
if ( count >= 100 )//如果达到任务指标
break;//中断线程执行
DoSomething();//完成某个任务
count++;}}}
通过互斥锁,使得一个线程在使用count字段的时候,其他所有的线程都无法使用,而被阻塞等待。达到了避免线程冲突的效果。
当然,这样的锁会使得这个多线程程序退化成同时只有一个线程在跑,所以我们可以把count++提前,使得lock的范围缩小,如这样:
void ThreadMethod()//运行在每个线程的方法
{
while( true )
{
lock ( typeof( Environment ) )
{
if ( count++ >= 100 )//如果达到任务指标
break;//中断线程执行
}
DoSomething();//完成某个任务
}}
最后来聊聊SyncRoot的问题。
用.NET的一定会有很多朋友困惑,为什么对一个容器加锁,需要这样写:
lock( Container.SyncRoot )
而不是直接lock( Container )
因为锁定一个容器并不能保证不会对这个容器进行修改,考虑这样一个容器:
public class Collection
{
private ArrayList _list;
public Add( object item )
{
_list.Add( item );
}
public object this[ int index ]
{
get { return _list[index]; }
set { _list[index] = value;}
}}
看起来,将
其lock起来后,就万事大吉了,没有人能修改这个容器,但实际上这个容器不过是用一个ArrayList实例来实现的,如果某段代码绕过这个容器而直接操作_list的话,则对这个容器对象lock也不可能保证容器不被修改了。