莫桑比克钱币:多线程存在的问题

来源:百度文库 编辑:偶看新闻 时间:2024/04/29 14:24:20

3.1.3 多线程存在的问题

多线程确实好用,但也有它的不足之处,当我们最初开始使用多线程时,它似乎是程序员的圣杯!谁不希望有几百个线程同时运行呢?谁不希望程序在性能上能够无缝地升级到二处理器、四处理器、甚至八处理器的服务器上呢?

接下来就开始试着使用线程。创建一些测试程序并运行它们,然后当一半线程毫无缘由地停止工作时我们对着显示器猛击键盘。是的,线程是造成这种情况的罪魁祸首。以下是线程挫伤人们积极性的几种方法。

1.线程使用更多的内存

如果了解现代计算机的工作原理,就会明白每个程序都有一个数据结构,这一数据结构称为“堆栈(stack)”,它了解正在执行的每个函数的本地数据。如果对堆栈的了解还不太多,也没关系,只需要知道程序有一个堆栈即可。程序堆栈通常需要相当数量的内存,这取决于有多少函数被调用。有些系统使用可变大小的堆栈,而有些系统则使用固定大小的堆栈。无论采用哪种方法,操作系统都力图要完全确保堆栈的大小足够大,以便在同时调用许多层函数时可以防止溢出。

注意:每个线程也了解其他一些事情,例如CPU的精确状态。但是,与其他所有事情相比,到目前为止,堆栈是线程最大的开销。

当使用多线程,而许多线程本身的运作类似于一个小程序时,会突然意识到每个线程都需要它自己的堆栈。如果计划对整个程序中的每个小任务都创建一个线程,则可能妨碍很大,因为即使是最小的线程也需要它自己的堆栈。

堆栈的大小范围可以小到几千字节,大到几兆字节,这与实现有关。因此在疯狂地创建几千个线程之前,我们需要牢记这一点。

2.线程要求更强的处理能力

在一个单处理器系统中,线程需要额外的处理开销,因为操作系统通过计算来确定什么时候应该运行哪个线程以及每个线程应该运行多长时间。如果有几千个线程,这就需要非常强的处理能力,从长远来看有可能减慢程序的执行速度。但是,对于使用阻塞IO调用的系统来说,线程处理是绝对必要的,实在没有什么方法可以绕开它。

但是,非常令人感到欣慰的是,多线程处理的了不起之处在于它的理论性。如果在一台两处理器机器上运行程序,则省去了大部分(而非全部)额外的处理开销,程序运行速度也比在单处理器机器上快。

警告:人们常常错误地认为,如果在一台两处理器机器上运行一个程序,则它的运行速度应该是单处理器机器上的两倍。但是,实际情况确实不是如此。四处理器机器和八处理器机器的情况也与此相同。这是由“收益递减定律(lawof diminishingreturns)”引起的。在机器上每增加一个新的处理器,则必然也增大了额外开销。到目前为止,n-处理器机器面临的最大问题是内存带宽(memorybandwidth)。在给定的时间内,只有一定量的内存能够从主存传送到每个处理器,因此,当开始增加处理器时,它们常常需要比内存总线能够处理的内存更多的内存。在这样的情况下,最终结果是系统中的大多数处理器在大部分时间内都在等待从内存中读取信息或者将信息写入到内存中,而不是真正地执行处理任务。

3.死锁

死锁是一个令人头疼的问题,仅仅只是提到它就足以使大多数程序员闻风而逃。如果不对线程与其他线程之间交互作用的每一种方法进行仔细考虑,相信大家都会遇到死锁    问题。

那么什么是死锁呢?假定有两个资源A和B,有两个线程1和2。线程1使用互斥锁将资源A锁住,并使用该资源。与此同时,由于线程2需要使用资源B,于是线程2将资源B锁住。片刻之后,线程1需要使用资源B,因此它也试图要锁住资源B。因为互斥锁在等待资源可以使用,所以线程1开始等待。接着,过了一会儿,线程2需要使用资源A,因此它也试图要锁住资源A,并且进入互斥锁循环等待。

这会发生什么情况呢?线程1正在等待的资源B为线程2所拥有,但是由于线程2正在等待的资源A为线程1所拥有,所以线程2并不会解除资源B的锁定。于是,两个线程都在等待另一个线程释放其资源,这就是死锁。

图3.7展示了这一过程,它是两个线程试图获取相同的资源而导致死锁的简单案例。

有许多情况可以产生死锁,但死锁不一定会立即显现出来。这是开发多线程程序所面临的最困难的问题之一。

图3.7 试图获取相同资源的两个线程导致了死锁

但是,本书中的程序一点也不复杂(关于线程处理这一点),因此死锁不应该是什么大问题。

4.数据损坏

以前已经提到过数据损坏,但在多线程处理中它是一个非常严重的问题,所以有必要再次提及它。如果不使用合理的同步结构(例如,互斥锁)来控制以独占方式访问数据,则数据损坏就是多线程处理中所面临的一个巨大问题。必须一直能绝对保证,只有在修改数据的线程知道没有其他线程与自己同时读取或者写入数据时,数据才能被修改。许多无法解释的bug都是由于数据争用而产生的。

5.调试

多线程程序的调试是很痛苦的一件事。由于大多数调试器并不支持多线程,所以要一步一步执行多线程程序极其困难。

更糟糕的是,有些支持多线程的调试器允许其他线程正常执行,而实质上正在调试的线程却被停止。这就使得在正常的程序中正确地进行同步极其困难。

最后,多线程程序是不确定的。这就意味着线程的启动和停止是由操作系统来控制的,而我们绝对无法控制它。在单线程程序中,可以模拟导致游戏崩溃的环境,方法是重复输入同样的信息,并使用相同的随机数生成器的种子,但是却无法模拟线程的执行顺序。正是由于这个原因,程序一百次可能有一次崩溃,但却不容易跟踪到崩溃的原因,因为导致崩溃的环境难以再现!