线程基础2

手动创建线程:

我们用代码来创建线程:
Dim MyThread as new Thread(AddressOf toExecuteSubOrFunctionName)
    MyThread.Start()
线程池典型是优选的方法,因为线程的创建和销毁需要代价,线程池可以重用已创建的线程。当手动创建线程时,你可能得承受或实现你自己的方案来重用你创建的线程。
然而手动创建自己的线程在有些场合仍是有用的,线程池被设计为这样的情形:后台的任务,运行了一会便完结了,这样使得后台线程可被重用于其他后续的后台任务(注意设计时任务可被映射到一个线程对象上)。如果你需要一个后台任务在整个程序生命区间都被执行,最好手动创建一个自己的后台线程。所以线程池最好用于短期任务的情形。
另一个手动创建线程的情形(场景)是:
当我们想在运行期与线程对象交互,我们可用多个方法作用于线程对象与之交互或控制后台的线程对象。线程支持的方法有:Abort,ApartmentState,Join,Priority,Sleep,Suspend,Resume.......。可用这些方法来控制线程的生命周期。这在高级线程场景中是很有用的。

**
共享数据:
在许多多线程场景中,我们在主线程中的数据需要被后台线程来访问,反之后台任务典型的产生供主线程使用的数据,这就是共享数据的例子。
多线程意味着在同一个进程中有多个线程,.net中意味在同一个AppDomain中有多个线程。当我们写多线程代码时,有一个棘手的问题就是在应用程序域中管理对共享数据的访问,我们不希望两个线程同时对一片内存区域进行写操作,或是多个线程来读取一个正在被别的线程进行更新的内存区,这种对内存访问的管理被称之为同步(注意有数据同步和方法同步,数据同步一般指互斥的读和写,而方法同步或动作同步是指如何使动作协调进行,比如一个方法必须等待另一个方法完成,或必须先于另一个方法。)就是这些同步问题才使得我们编写多线程代码变得如此困难。
**
***

***
当多个线程欲同时访问一块共享的数据,使用同步来控制这些访问,典型的是除了一个线程外其余线程统统锁住。只有一个线程可以访问共享的数据,其他所有的线程被置于等待状态(使用某种排序的阻塞操作),一旦那个非阻塞的线程完成对共享数据的访问,阻塞会被解除,此时允许其他线程回复处理,并可访问共享数据。(不是我的思想有问题,每次看到线程问题,我都会想到古代青楼嫖妓事件,比如你去嫖妓,每次那肯定就只能一位一位的来啊,后面的排队吧,至于怎么排,或许谁官大谁排前面,同级别的官那就FIFO——谁早谁前面,那要是皇帝老子来了,其他的都靠后吧。这个思想源于周星驰的《九品芝麻官》,另一个有助于理解同步的就是,上卫生间,比如火车上的卫生间,那都是带锁和指示标识的。我举上面两个例子主要是为了帮助记忆和理解问题,至于我个人的思想————我想没什么问题,条件反射不受控制吧)。

解除阻塞的处理经常被称为事件,当我们说“事件”时,我们不是在谈论VB或c#中的事件event,但它们有着相同的概念:一些事情发生了,我们对该事情作出反应。
这里是指,唯一的那个非阻塞线程激发了一个事件,并导致释放了其他的线程以便让它们可访问共享数据。

尽管“阻塞”可被用来控制线程的执行,但它主要被用来控制对资源的访问,包括内存。这是最基本的同步思想————如果你需要某物,对其加锁,访问它,并释放锁(或你被阻塞直到可以访问)。
同步是昂贵的并且可能很复杂。昂贵是指它在使用共享数据时阻止了一个或多个线程的运行。使用多线程的初衷是"同时"运行多个线程,如果你经常阻塞多个线程仅让一个线程运行,这时你就失去了多线程的优势(阻塞多个线程,仅让一个线程运行也可称为“顺序化”)。说他复杂是因为有很多方法实现同步,每种技术都只适合特定类型的同步问题,如果使用了错误的技术或使用在了错误的地方都会提高同步的代价。
同步也经常带来“死锁”问题,指两个或两个以上的线程都处于等待状态,这种等待是永久的,它们都在等其他的线程释放锁,死锁出现在多线程,访问多资源的情况下,就是“持有锁,并还想要锁”关于死锁问题可参考(数据库或java并发编程设计与模式)。你肯定看到过死锁情况,你可能经常使用任务管理器来停止一些进程,来缓解死锁,有一种资源图,可用来帮助发现死锁(出现环状路径)。即使是商业软件也可能出现死锁问题(如微软的Microsoft Outlook),所以请谨慎处理同步。

*******
因为共享数据可能引起性能问题并提升问题的复杂性,所以应避免或最小化的使用这种技术,一般共享数据是不可避免的,所以问题转化为如何管理这些共享的数据来避免或最小化同步,主要有两种技术:
1.传递数据拷贝(针对值类型而非引用类型)
避免数据共享的第一种方式是:总是在线程间传递数据的引用(副本)。如果你能确保那些线程使用了同一个引用,那么每个线程将拥有一份数据的拷贝,并且不会访问到正被其他线程使用到的数据。
2.传递数据关系
以上所说的均指值类型,如Integer ,或是不可变对象如:String,对于引用类型不起作用的。因为引用类型从来不会按值传递,它们总是按引用传递。所以针对引用类型我们需要更改策略,不会返回一个数据的拷贝,而是返回一个包含数据的对象的引用。之后我们需要确保后台任务停止使用那个对象,并开始使用一个新的对象,只要不同的线程不同时使用同一个对象,那么就不会产生冲突。当我们依赖于传递数据关系时,需要确保任何时刻仅有一个线程访问数据,确保一旦后台线程将对象返回给主线程它就再也不会访问那个对象(虽然它也有数据的引用)。


*******
********
使用同步来共享数据

不正确的同步实现可能导致性能问题,死锁,或应用程序崩溃。成功取决于细节(Success is dependent on serious attention to detail)。测试时不会发现所有的问题,但当问题发生于生产时,总是很悲惨的。请像数学家证明数学问题那样,尽可能的逻辑的分析一切可能吧!

.net框架中的一些对象有内置的同步支持,所以不必我们自己来实现同步,多数面向集合的类都支持同步,包括:Queue,Stack,Hashtable,ArrayList,等等。
注意它们的特殊创建方式:
dim mySyncList as ArrayList=ArrayList.Synchronized(new ArrayList)

比较特殊的创建方式,我没有看过源码实现,但我猜测,是使用了代理设计模式,或是装饰器设计模式。这样封装后的结果使得我们拥有了一个线程安全的ArrayList对象,它会自动阻止多个线程以无效的方式来访问数据。
对集合访问时我们经常使用For Each语句,但它即使对同步化的集合也不是线程安全的,所有我们一般需要在一个SyncLock块中封装整个循环体:
SyncLock mySyncList.SyncRoot
    for each item as MyType in mysycList
    do something with item.......
    Next
end SyncLock

VB 中的SyncLock语句被用来在一个对象上提供互斥锁。这里 从一个同步化的ArrayList对象的SyncRoot得到一个互斥锁。这意味所有位于SyncLock块中的代码可被确保它是唯一与ArrayList内容交互的代码,再没有其他的线程在你的代码处于块中时数据被访问(注意锁也存在粒度问题,锁一个对象的方法,只确保对象的该方法不会被同时访问,但该对象的其他方法仍可被访问,但如果你锁住对象本身,那么所有属于该对象的方法不会被同时访问。粒度概念:举例而言,就像你锁住一个庭院的大门,和锁住庭院中某栋楼的某个房间,它们是不同的粒度。锁粒度影响重用性(并发度))。

同步对象:
尽管许多集合对象可选的支持同步,但.net框架中的大部分对象或是第三方的库仍是非线程安全的。想安全的在多线程环境中使用这些非线程安全的对象,我们需要手动实现同步。
手动实现同步,我们必须依赖于Windows操作系统,.net框架包含一些类,它们封装了底层的Windows操作系统概念,所以我们不必直接调用Windows。我们可以使用.net框架提供的同步对象。
同步对象有其特有的术语,多数这样的对象可被释放和获取,某写些时候你需要在一个对象上等待直到被信号通知。

一个对象可被获取,思想是:当我们有一个对象时,我们就有一个锁,其他任何线程试图获取该对象,线程都会被阻塞直到我们释放了该对象,当不必要时就不应该继续持有这样的对象,因为锁机制会降低整个系统的性能。java中任何一个对象都可以是一个锁,也可以用其他对象作为自己的锁(比如常使用Object实例作为锁)。
在对象上等待:意味着你的线程被阻塞,其他的线程会通知你的对象,被通知后你的线程才可能从阻塞变为解阻塞。多个线程可能等待在同一个对象上(看上面的嫖妓例子来理解.....:-)...呵呵)当一个对象被通知(signaled——发送了信号)所有的线程都会解阻塞,之后重新竞争对象(不是仅最有可能的下一个线程被通知,而是全部!!)。以下给出.net框架提供的主要的同步对象:
AutoresetEvent :该对象允许一个线程释放其他等待在该对象上的线程。是Wait/Signal模型。

InterLocked对象:允许多个线程安全地递增或递减存于对所有线程都可访问的变量中的值。

ManualResetEvent:该对象运行一个线程释放其他等待在那个对象上的线程,是 Wait/Signal模型。

Monitor:该对象,定义一个互斥的应用程序级别的锁,任何给定时刻只能用一个线程拥有锁。是Acquire/Release模型。

Mutex:定义一个互斥的系统范围内的锁,任何给定时刻仅有一个线程可拥有锁,也是Acquire/Release模型,注意与上面对象的粒度不同,这个更大些,进程间而言。

ReaderWriterLock:定义一个锁,允许多个线程读数据,当仅允许一个写性质的线程写数据。是Acquire/Release模型。

ReaderWriterLockSlim:定义一个锁,允许多个线程读数据,但互斥的访问会提供给一个写线程,这是一个.netFrameWork3.5的新对象。是Acquire/Release模型。
********
*********
互斥锁与SyncLock语句:
主要有两种技术用于互斥性锁:monitor和mutex对象。

monitor对象允许进程中的一个线程阻塞同一进程中的其他线程。而mutex对象允许任何进程中的线程阻塞任何其他进程中的线程(如果有其他进程也想访问该对象的话,比如文件,就是这样的跨进程资源)。因为mutex是一个系统范围的对象,是更加昂贵的对象,应仅被用于跨进程访问对象的情形。

VB包括SyncLock语句,它是一个访问monitor对象的捷径,但我们仍可直接创建和使用Stystem.Threading.Monitor对象,而使用Synclock语句会更简便些。
互斥锁可被用来保护共享数据,以便在一个时刻仅有一个线程可访问数据。它也可被用来确保一个时刻仅有一个线程能够运行一段代码(那段代码,操作系统知识中称为“警戒区”),警戒区在计算机科学中是一个很重要的概念,它比使用锁来保护数据更普遍。

可以使用互斥锁来锁定几乎是任何的共享数据。Syclock语句通过一个对象来起作用:
SyncLock obj1
’ blocks against obj1
End SyncLock

SyncLock obj2
’ blocks against obj2
End SyncLock

*********
**********
读写锁:
待续..............................

**********

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值