.net线程核心处理机制(1)

一个线程池阻塞(block)时,线程池会创建额外的线程,而创建、销毁和调度线程所需的时间和内存资源是相当昂贵的。另外,许多开发人员在看见自己程序中的线程没有在做任何有用的事情时,他们的习惯时创建更多的线程,期望新线程能做有用的事情。为了构建可伸缩的、具有良好响应能力的应用程序,关键在于不要阻塞你拥有的线程,使它们能用于执行其他任务。

多个线程同时访问共享资源时,线程同步用于防止数据损坏。我之所以强调“同时”,是因为线程同步问题就是计时问题。如果有一些数据需要由两个线程访问,但那些线程不可能同时接触到数据,就完全不需要线程同步。在ClientConnected方法中,一个线程分配了Byte[]. 该数组将由客户端发送的数据填充。客户端发送数据时,一个不同的线程池线程会执行GotRequest方法这个方法会处理Byte[]中的数据。在这种情况下,确实由两个不同的线程访问相同的数据。但是根据应用程序的建构方式,不可能由两个线程同时访问同一个Byte[]. 因此,在命名管道应用程序中,完全不需要线程同步。

不需要线程同步是最理想的情况,因为线程同步存在许多问题。第一个问题是它比较繁琐,而且很容易写错。在你的代码中,必须标识出可能由多个线程同时访问的所有数据。然后必须用额外的代码将这些代码包围起来,并获取和释放一个线程同步锁。锁的作用时确保一次只有一个线程访问资源。只要有一个代码块忘记用锁包围,数据就会损坏。另外,没有办法证明你已经正确添加了所有锁定代码。必须运行应用程序,对它进行大量压力测试,并寄希望于没有什么地方出错。事实上,应该在由尽量多的CPU的机器上测试应用程序。因为CPU越多,两个或多个线程同时访问的资源的机率越大,越容易检测到问题。

锁的第二个问题在于,它们会损害性能。获取和释放一个锁是需要时间的,因为要调用一些额外的方法,而且不同的CPU必须进行协调,以决定哪个线程先取得锁。让机器中的CPU以这种方式相互通信,会对性能造成影响。例如假定使用以下代码将一个节点添加到链表头:

// 这个类由linkedList 类使用
public class Node{
	internal Node m_next;
	//其他成员未列出
}

public sealed class LinkedList{
	private Node m_head;
	public void Add(Node newNode){
		//以下两行执行速度非常快的引用赋值
		newNode.m_text = m_head;
		m_head = newhead;
	}
}

这个Add方法只是执行两个引用赋值,可以相当快地执行。现在,假定要使Add方法线程安全,使多个线程能同时调用它而不至于损坏链表,就需要让Add方法获取和释放一个锁:
public sealed class LinkedList{
private SomeKindOfLock m_lock = new SomeKindOfLock();
private Node m_head;

public void Add(Node newNode){
	m_lock.Acquire();
	//  以下两行执行速度非常块的引用赋值
	newNode.m_next = m_head;
	m_head = newNode;
	m_lock.Release();
}

}

Add虽然线程安全了,但速度也显著慢下来了。具体慢了多少,要取决于所选的锁的种类;但即便是最快的锁,也会造成Add方法数倍地慢于没有任何锁的版本。另外,如果代码在一个循环中调用Add向链表插入几个节点,性能还会变得更糟。

线程同步锁的第三个问题在于,它们一次只允许一个线程访问资源。这是锁的全部意义之所在,但也是问题之所在,因为阻塞一个线程会造成更多的线程被创建。例如,假定一个线程池试图获取一个它暂时无法获取的锁,线程池就可能创建一个新线程,使CPU保持“饱和”。创建线程时一个昂贵的操作,会耗费大量的内存和时间。更不妙的时,当阻塞的线程再次运行时,它会和这个新的线程池线程共同运行。也就是说,windows现在要调度比CPU数量更多的线程,这会增大上下文切换的机率,而上下文切换也会损害到性能。

综上所述,线程同步是一件不好的事情,所以在设计自己的应用程序时,应该尽可能地避免进行线程同步。为此,要避免使用一些共享数据,比如静态字段。线程用new操作符构造一个对象时,new操作符会返回对新对象的一个引用。在这个时刻,只有构造对象的线程才有对它的引用;其他任何线程都不能访问那个对象。如果你能一直避免将这个引用传给可能同时使用对象的另一个线程,就不必对该对象的访问。

试着使用值类型,因为它们总是会被副本,所以每个线程操作的都是它自己的拿个副本。

最后,多个线程同时对共享数据进行只读访问是没有任何问题的。例如,许多应用程序都会在它们初始化期间创建一些数据结构。一旦初始化,应用程序就可以创建它希望的任何数量的线程;如果所有线程都只是查询数据,那么所有线程都能同时查询,无需获取和释放锁。String类型便是这样的一个例子:一旦创建好String对象,它就是“不可变”(immuteable)的。所以,许多线程能同时访问一个String对象,String对象没有被破坏之虞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值