文档库 最新最全的文档下载
当前位置:文档库 › C#多线程

C#多线程

C#多线程
C#多线程

多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄

本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,

希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释。让我们来看看这几个类的关系图:

1.lock关键字

lock是C#关键词,它将语句块标记为临界区,确保当一个线程位于代码的临界区时,另一个线程

不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。方法是获取给定对象的互斥锁,执行语句,然后释放该锁。

MSDN上给出了使用lock时的注意事项通常,应避免锁定public 类型,否则实例将超出代码的控制范围。常见的结构lock (this)、lock (typeof (MyType)) 和lock ("myLock") 违反此准则。

1)如果实例可以被公共访问,将出现lock (this) 问题。

2)如果MyType 可以被公共访问,将出现lock (typeof (MyType)) 问题由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例。微

软现在建议不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他

线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。

3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现lock(“myLock”) 问题。这个问题和.NET Framework创建字符串的机制有关系,如果两个string变量值都是"myLock",在内存中会指向同一字符串对象。

最佳做法是定义private 对象来锁定, 或private static对象变量来保护所有实例所共有的数据。

我们再来通过IL Dasm看看lock关键字的本质,下面是一段简单的测试代码:

lock (lockobject)

{

int i = 5;

}

用IL Dasm打开编译后的文件,上面的语句块生成的IL代码为:

IL_0045:call void [mscorlib]System.Threading.Monitor::Enter(object)

IL_004a:nop

.try

{

IL_004b:nop

IL_004c:ldc.i4.5

IL_004d:stloc.1

IL_004e:nop

IL_004f:leave.s IL_0059

} // end .try

finally

{

IL_0051:ldloc.3

IL_0052:call void [mscorlib]System.Threading.Monitor::Exit(object)

IL_0057:nop

IL_0058:endfinally

} // end handler

通过上面的代码我们很清楚的看到:lock关键字其实就是对Monitor类的Enter()和Exit()方法的封装,并通过try...catch...finally语句块确保在lock语句块结束后执行Monitor.Exit()方法,释

放互斥锁。

2.Monitor类

Monitor类通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问临界区的能力。当一个线程拥有对象的锁时,其他任何线程都不能获取该锁。还可以使用Monitor 来确保不会

允许其他任何线程访问正在由锁的所有者执行的应用程序代码节,除非另一个线程正在使用其他的锁定对象执行该代码。

通过对lock关键字的分析我们知道,lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。

另外Monitor类还有几个常用的方法:

TryEnter()能够有效的解决长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值

来决定是否继续下面的操作。

Wait()释放对象上的锁以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。脉冲信号用于通知等待线程有关对象状态的更改。

Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更

改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。

注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。

我们假定一种情景:妈妈做蛋糕,小孩有点馋,妈妈每做好一块就要吃掉,妈妈做好一块后,告诉小孩蛋糕已经做好了。下面的例子用Monitor类的Wait和Pulse方法模拟小孩吃蛋糕的情景。

//仅仅是说明Wait和Pulse/PulseAll的例子

//逻辑上并不严密,使用场景也并不一定合适

class MonitorSample

{

private int n = 1; //生产者和消费者共同处理的数据

private int max = 10000;

private object monitor = new object();

public void Produce()

{

lock (monitor)

{

for (; n <= max; n++)

{

Console.WriteLine("妈妈:第" + n.ToString() + "块蛋糕做好了");

//Pulse方法不用调用是因为另一个线程中用的是Wait(object,int)方法

//该方法使被阻止线程进入了同步对象的就绪队列

//是否需要脉冲激活是Wait方法一个参数和两个参数的重要区别

//Monitor.Pulse(monitor);

//调用Wait方法释放对象上的锁并阻止该线程(线程状态为WaitSleepJoin)

//该线程进入到同步对象的等待队列,直到其它线程调用Pulse使该线程进入到就绪队列中

//线程进入到就绪队列中才有条件争夺同步对象的所有权

//如果没有其它线程调用Pulse/PulseAll方法,该线程不可能被执行

Monitor.Wait(monitor);

}

}

}

public void Consume()

{

lock (monitor)

{

while (true)

{

//通知等待队列中的线程锁定对象状态的更改,但不会释放锁

//接收到Pulse脉冲后,线程从同步对象的等待队列移动到就绪队列中 //注意:最终能获得锁的线程并不一定是得到Pulse脉冲的线程

Monitor.Pulse(monitor);

//释放对象上的锁并阻止当前线程,直到它重新获取该锁

//如果指定的超时间隔已过,则线程进入就绪队列

Monitor.Wait(monitor,1000);

Console.WriteLine("孩子:开始吃第" + n.ToString() + "块蛋糕");

}

}

}

static void Main(string[] args)

{

MonitorSample obj = new MonitorSample();

Thread tProduce = new Thread(new ThreadStart(obj.Produce));

Thread tConsume = new Thread(new ThreadStart(obj.Consume));

//Start threads.

tProduce.Start();

tConsume.Start();

Console.ReadLine();

}

}

这个例子的目的是要理解Wait和Pulse如何保证线程同步的,同时要注意Wait(obeject)和Wait(object,int)方法的区别,理解它们的区别很关键的一点是要理解同步的对象包含若干引用,其中

包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。

本篇继续介绍WaitHandler类及其子类Mutex,ManualResetEvent,AutoResetEvent的用法。.NET 中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?其实,我们抛开.NET环境看线程同步,无非是执行两种操作:一是互斥/加锁,目的是保证临界区代码操作的“原子性”;另一种是信号灯操作,目的是保证多个线程按照一定顺序执行,如生产者线程要先于消费者线程执行。.NET中线程同步的类无非是对这两种方式的封装,目的归根结底都可以归结为实现互斥/加锁或者是信号灯这两种方式,只是它们的适用场合有所不。下面我们根据类的层次结构了解WaitHandler及其子类。

1.WaitHandler

WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,

ManualResetEvent共同的祖先,它封装Win32同步句柄内核对象,也就是说是这些内核对象的托管版本。

线程可以通过调用WaitHandler实例的方法WaitOne在单个等待句柄上阻止。此外,WaitHandler类重载了静态方法,以等待所有指定的等待句柄都已收集到信号WaitAll,或者等待某一指定的等待句柄收集到信号WaitAny。这些方法都提供了放弃等待的超时间隔、在进入等待之前退出同步上下文的机会,并允许其它线程使用同步上下文。WaitHandler是C#中的抽象类,不能实例化。

2.EventWaitHandler vs. ManualResetEvent vs. AutoResetEvent(同步事件)

我们先看看两个子类ManualResetEvent和AutoResetEvent在.NET Framework中的实现:

//.NET Framework中ManualResetEvent类的实现

[ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]

public sealed class ManualResetEvent : EventWaitHandle

{

// Methods

public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset)

{

}

}

//.NET Framework中AutoResetEvent类的实现

[ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true,

ExternalThreading = true)]

public sealed class AutoResetEvent : EventWaitHandle

{

// Methods

public AutoResetEvent(bool initialState)

: base(initialState, EventResetMode.AutoReset)

{

}

}

原来ManualResetEvent和AutoResetEvent都继承自EventWaitHandler,它们的唯一区别就在于父类EventWaitHandler的构造函数参数EventResetMode不同,这样我们只要弄清了参数EventResetMode值不同时,EventWaitHandler类控制线程同步的行为有什么不同,两个子类也就

清楚了。为了便于描述,我们不去介绍父类的两种模式,而直接介绍子类。

ManualResetEvent和AutoResetEvent的共同点:

1)Set方法将事件状态设置为终止状态,允许一个或多个等待线程继续;Reset方法将事件状

态设置为非终止状态,导致线程阻止;WaitOne阻止当前线程,直到当前线程的WaitHandler收到事件信号。

2)可以通过构造函数的参数值来决定其初始状态,若为true则事件为终止状态从而使线程为非阻塞状态,为false则线程为阻塞状态。

3)如果某个线程调用WaitOne方法,则当事件状态为终止状态时,该线程会得到信号,继续向下执行。

ManualResetEvent和AutoResetEvent的不同点:

1)AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待,也

就是说AutoResetEvent一次只唤醒一个线程;

2)ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。

3)也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将

一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。

示例场景:张三、李四两个好朋友去餐馆吃饭,两个人点了一份宫爆鸡丁,宫爆鸡丁做好需要一

段时间,张三、李四不愿傻等,都专心致志的玩起了手机游戏,心想宫爆鸡丁做好了,服务员肯定会叫我们的。服务员上菜之后,张三李四开始享用美味的饭菜,饭菜吃光了,他们再叫服务员过来买单。我们可以从这个场景中抽象出来三个线程,张三线程、李四线程和服务员线程,他们之间需要同步:

服务员上菜—>张三、李四开始享用宫爆鸡丁—>吃好后叫服务员过来买单。这个同步用什么呢?ManualResetEvent还是AutoResetEvent?通过上面的分析不难看出,我们应该用ManualResetEvent进行同步,下面是程序代码:

public class EventWaitTest

{

private string name; //顾客姓名

//private static AutoResetEvent eventWait = new AutoResetEvent(false);

private static ManualResetEvent eventWait = new ManualResetEvent(false);

private static ManualResetEvent eventOver = new ManualResetEvent(false);

public EventWaitTest(string name)

{

https://www.wendangku.net/doc/7813264407.html, = name;

}

public static void Product()

{

Console.WriteLine("服务员:厨师在做菜呢,两位稍等");

Thread.Sleep(2000);

Console.WriteLine("服务员:宫爆鸡丁好了");

eventWait.Set();

while (true)

{

if (eventOver.WaitOne(1000, false))

{

Console.WriteLine("服务员:两位请买单");

eventOver.Reset();

}

}

}

public void Consume()

{

while (true)

{

if (eventWait.WaitOne(1000, false))

{

Console.WriteLine(https://www.wendangku.net/doc/7813264407.html, + ":开始吃宫爆鸡丁");

Thread.Sleep(2000);

Console.WriteLine(https://www.wendangku.net/doc/7813264407.html, + ":宫爆鸡丁吃光了");

eventWait.Reset();

eventOver.Set();

break;

}

else

{

Console.WriteLine(https://www.wendangku.net/doc/7813264407.html, + ":等着上菜无聊先玩会手机游戏");

}

}

}

}

public class App

{

public static void Main(string[] args)

{

EventWaitTest zhangsan = new EventWaitTest("张三");

EventWaitTest lisi = new EventWaitTest("李四");

Thread t1 = new Thread(new ThreadStart(zhangsan.Consume));

Thread t2 = new Thread(new ThreadStart(lisi.Consume));

Thread t3 = new Thread(new ThreadStart(EventWaitTest.Product));

t1.Start();

t2.Start();

t3.Start();

Console.Read();

}

}

编译后查看运行结果,符合我们的预期,控制台输出为:

服务员:厨师在做菜呢,两位稍等...

张三:等着上菜无聊先玩会手机游戏

李四:等着上菜无聊先玩会手机游戏

张三:等着上菜无聊先玩会手机游戏

李四:等着上菜无聊先玩会手机游戏

服务员:宫爆鸡丁好了

张三:开始吃宫爆鸡丁

李四:开始吃宫爆鸡丁

张三:宫爆鸡丁吃光了

李四:宫爆鸡丁吃光了

服务员:两位请买单

如果改用AutoResetEvent进行同步呢?会出现什么样的结果?恐怕张三和李四就要打起来了,一个享用了美味的宫爆鸡丁,另一个到要付账的时候却还在玩游戏。感兴趣的朋友可以把注释的那行代码注释去掉,并把下面一行代码注释掉,运行程序看会出现怎样的结果。

3.Mutex(互斥体)

Mutex和EventWaitHandler有着共同的父类WaitHandler类,它们同步的函数用法也差不多,这里不再赘述。Mutex的突出特点是可以跨应用程序域边界对资源进行独占访问,即可以用于同步不同进程中的线程,这种功能当然这是以牺牲更多的系统资源为代价的。

前两篇简单介绍了线程同步lock,Monitor,同步事件EventWaitHandler,互斥体Mutex的基本用法,在此基础上,我们对它们用法进行比较,并给出什么时候需要锁什么时候不需要的几点建议。最后,介绍几个FCL中线程安全的类,集合类的锁定方式等,做为对线程同步系列的完善和补充。

1.几种同步方法的区别

lock和Monitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方面可能更为有效,同步速度较快,但不能跨进程同步。lock(Monitor.Enter 和Monitor.Exit方法的封装),主要作用是锁定临界区,使临界区代码只能被获得锁的线程执行。Monitor.Wait和Monitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死锁。

互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热闹。

EventWaitHandle 类允许线程通过发信号互相通信。通常,一个或多个线程在EventWaitHandle 上阻止,直到一个未阻止的线程调用Set 方法,以释放一个或多个被阻止的线程。

2.什么时候需要锁定

首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要呢?

1)只有共享资源才需要锁定

只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。

2)多使用lock,少用Mutex

如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清楚的了解到他们的不同和适用范围。

3)了解你的程序是怎么运行的

实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁定,当然对于https://www.wendangku.net/doc/7813264407.html,中的Application对象中的数据,我们就要考虑加锁了。

4)把锁定交给数据库

数据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。

5)业务逻辑对事务和线程安全的要求

这条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲一些性能,和很多的开发时间来做这方面的工作。而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一,对结果无伤大雅的情况下,我们就可以不用去管它。

3.InterLocked类

Interlocked 类提供了同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改和存储该值之前被挂起。

我们来看一个InterLock.Increment()的例子,该方法以原子的形式递增指定变量并存储结果,示例如下:

class InterLockedTest

{

public static Int64 i = 0;

public static void Add()

{

for (int i = 0; i < 100000000; i++)

{

Interlocked.Increment(ref InterLockedTest.i);

//InterLockedTest.i = InterLockedTest.i + 1;

}

}

public static void Main(string[] args)

{

Thread t1 = new Thread(new ThreadStart(InterLockedTest.Add)); Thread t2 = new Thread(new ThreadStart(InterLockedTest.Add));

t1.Start();

t2.Start();

t1.Join();

t2.Join();

Console.WriteLine(InterLockedTest.i.ToString());

Console.Read();

}

}

输出结果200000000,如果InterLockedTest.Add()方法中用注释掉的语句代替Interlocked.Increment()方法,结果将不可预知,每次执行结果不同。InterLockedTest.Add()方法保证了加1操作的原子性,功能上相当于自动给加操作使用了lock锁。同时我们也注意到InterLockedTest.Add()用时比直接用+号加1要耗时的多,所以说加锁资源损耗还是很明显的。

另外InterLockedTest类还有几个常用方法,具体用法可以参考MSDN上的介绍。

4.集合类的同步

.NET在一些集合类,比如Queue、ArrayList、HashTable和Stack,已经提供了一个供lock 使用的对象SyncRoot。用Reflector查看了SyncRoot属性(Stack.SynchRoot略有不同)的源码如下:

public virtual object SyncRoot

{

get

{

if (this._syncRoot == null)

{

//如果_syncRoot和null相等,将new object赋值给_syncRoot

//https://www.wendangku.net/doc/7813264407.html,pareExchange方法保证多个线程在使用syncRoot时是线程安全的

https://www.wendangku.net/doc/7813264407.html,pareExchange(ref this._syncRoot, new object (), null);

}

return this._syncRoot;

}

}

这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。应该使用下面的代码:

Queue q = new Queue();

lock (q.SyncRoot)

{

foreach (object item in q)

{

//do something

}

}

还有一点需要说明的是,集合类提供了一个是和同步相关的方法Synchronized,该方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock关键字进行了同步处理。如HashTable的Synchronized返回一个新的线程安全的HashTable实例,代码如下:

//在多线程环境中只要我们用下面的方式实例化HashTable就可以了

Hashtable ht = Hashtable.Synchronized(new Hashtable());

//以下代码是.NET Framework Class Library实现,增加对Synchronized的认识

[HostProtection(SecurityAction.LinkDemand, Synchronization=true)] public static Hashtable Synchronized(Hashtable table)

{

if (table == null)

{

throw new ArgumentNullException("table");

}

return new SyncHashtable(table);

}

//SyncHashtable的几个常用方法,我们可以看到内部实现都加了lock关键字保证线程安全

public override void Add(object key, object value)

{

lock (this._table.SyncRoot)

{

this._table.Add(key, value);

}

}

public override void Clear()

{

lock (this._table.SyncRoot)

{

this._table.Clear();

}

}

public override void Remove(object key)

{

lock (this._table.SyncRoot)

{

this._table.Remove(key);

}

}

线程同步是一个非常复杂的话题,这里只是根据公司的一个项目把相关的知识整理出来,作为工作的一种总结。这些同步方法的使用场景是怎样的?究竟有哪些细微的差别?还有待于进一步的学习和实践。

多线程编程的详细说明完整版

VB .NET多线程编程的详细说明 作者:陶刚整理:https://www.wendangku.net/doc/7813264407.html, 更新时间:2011-4-1 介绍 传统的Visual Basic开发人员已经建立了同步应用程序,在这些程序中事务按顺序执行。尽管由于多个事务多多少少地同时运行使多线程应用程序效率更高,但是使用先前版本的Visual Basic很难建立这类程序。 多线程程序是可行的,因为操作系统是多任务的,它有模拟同一时刻运行多个应用程序的能力。尽管多数个人计算机只有一个处理器,但是现在的操作系统还是通过在多个执行代码片断之间划分处理器时间提供了多任务。线程可能是整个应用程序,但通常是应用程序可以单独运行的一个部分。操作系统根据线程的优先级和离最近运行的时间长短给每一个线程分配处理时间。多线程对于时间密集型事务(例如文件输入输出)应用程序的性能有很大的提高。 但是也有必须细心的地方。尽管多线程能提高性能,但是每个线程还是需要用附加的内存来建立和处理器时间来运行,建立太多的线程可能降低应用程序的性能。当设计多线程应用程序时,应该比较性能与开销。 多任务成为操作系统的一部分已经很久了。但是直到最近Visual Basic程序员才能使用无文档记录特性(undocumented)或者间接使用COM组件或者操作系统的异步部分执行多线程事务。.NET框架组件为开发多线程应用程序,在System.Threading名字空间中提供了全面的支持。 本文讨论多线程的好处以及怎样使用Visual Basic .NET开发多线程应用程序。尽管Visual Basic .NET和.NET框架组件使开发多线程应用程序更容易,但是本文作了调整使其适合高级读者和希望从早期Visual Basic转移到Visual Basic .NET的开发人员。 多线程处理的优点 尽管同步应用程序易于开发,但是它们的性能通常比多线程应用程序低,因为一个新的事务必须等待前面的事务完成后才能开始。如果完成某个同步事务的时间比预想的要长,应用程序可能没有响应。多线程处理可以同时运行多个过程。例如,字处理程序能够在继续操作文档的同时执行拼写检查事务。因为多线程应用程序把程序分解为独立的事务,它们能通过下面的途径充分提高性能: l 多线程技术可以使程序更容易响应,因为在其它工作继续时用户界面可以保持激活。 l 当前不忙的事务可以把处理器时间让给其它事务。 l 花费大量处理时间的事务可以周期性的把时间让给其它的事务。 l 事务可以在任何时候停止。 l 可以通过把单独事务的优先级调高或调低来优化性能。 明确地建立多线程应用程序的决定依赖于几个因素。多线程最适合下面的情况:

多线程与并发面试题

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,可是你依然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它能够被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程能够被称为轻量级进程。线程需要较少的资源来创立和驻留在进程中,而且能够共享进程中的资源。 2. 多线程编程的好处是什么?

在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创立多个线程去执行一些任务会比创立多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创立一个线程,它就被称为用户线程。一个守护线程是在后台执行而且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序而且退出。一个守护线程创立的子线程依然是守护线程。 4. 我们如何创立一个线程? 有两种创立线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创立一个Thread对象;二是直接继承Thread类。若想了解更多能够阅读这篇关于如何在Java中创立线程的文章。 5. 有哪些不同的线程生命周期?

第二章多线程分布式计算课后答案

第二章 1. 选择题 12345678910 D A B C C E C A B B 1112131415161718 B B B C A D A A 2. 程序/方法与进程/线程有什么不同?(53页第四段) 答:一个程序/方法是由程序员写的一段代码,它是静态的。进程/线程是由执行的程序/方法、当前值、状态信息和用于支持它执行的资源构成,资源是它执行时的动态因素。换言之,一个进程/线程是一个动态实体,只有当程序或函数执行时才会存在。 3. 比较多进程(多任务)操作系统和多线程编程环境。(53页5、 6、7段) 答:为了真正并行执行多个进程/线程,必须存在多个处理器。如果系统中只有一个处理器,表面上多个进程/线程执行,实际上实在分时模式下顺序执行。 从同一代码块可以创建多个进程/线程。默认情况下,包含在不同进程/线程中的代码和数据是分离的,每一个都有它自己执行代码的副本、局部变量的栈、对象数据区以及其他数据元素。 通常情况下,一个分布式操作系统可以由不同电脑上的多个实例或副本构成,每一个实例或副本都可以管理多个进程。同样,每个进程可以是由多个线程组成的一个多线程程序。 4. 什么是临界操作?用什么方法可以来保护临界操作?(54页第1 段) 答:对共享资源的访问称为临界操作。虽然一个简单的锁定可以防止共享资源被访问,但是也消除了并行处理的可能性。更理想的方法是不锁定并行读操作,而锁定并行读-写和写-写组合。 5. 什么是死锁?哪些策略可以用来解决死锁问题?(55页) 答:死锁的情况是两个或多个竞争操作等待对方完成,导致都不能完成。 解决方法: (1) 死锁预防:使用一种算法可以保证不会发生死锁。 (2) 死锁避免:使用一种算法,能够遇见死锁的发生从而拒绝资源请求、

现代操作系统第四版 第二章 答案

现代操作系统第二章进程与线程习题 1. 图2-2中给出了三个进程状态,在理论上,三个状态可以有六种转换,每个状态两个。但是,图中只给出了四种转换。有没有可能发生其他两种转换中的一个或两个 A:从阻塞到运行的转换是可以想象的。假设某个进程在I/O上阻塞,而且I/O结束,如果此时CPU空闲,该进程就可以从阻塞态直接转到运行态。而另外一种转换(从阻塞态到就绪态)是不可能的。一个就绪进程是不可能做任何会产生阻塞的I/O或者别的什么事情。只有运行的进程才能被阻塞。 2.假设要设计一种先进的计算机体系结构,它使用硬件而不是中断来完成进程切换。CPU需要哪些信息请描述用硬件完成进程切换的工作过程。 A:应该有一个寄存器包含当前进程表项的指针。当I/O结束时,CPU将把当前的机器状态存入到当前进程表项中。然后,将转到中断设备的中断向量,读取另一个过程表项的指针(服务例程),然后,就可以启动这个进程了。 3.当代计算机中,为什么中断处理程序至少有一部分是用汇编语言编写的 A:通常,高级语言不允许访问CPU硬件,而这种访问是必需的。例如,中断处理程序可能需要禁用和启用某个特定设备的中断服务,或者处理进程堆栈区的数据。另外,中断服务例程需要尽快地执行。(补充)主要是出于效率方面的考量。中断处理程序需要在尽量短的时间内完成所需的必要处理,尽量减少对线程/程序流造成的影响,因此大部分情况下用汇编直接编写,跳过了通用编译过程中冗余的适配部分。 4.中断或系统调用把控制转给操作系统时,为什么通常会用到与被中断进程的栈分离的内核栈 A:内核使用单独的堆栈有若干的原因。其中两个原因如下:首先,不希望操作系统崩溃,由于某些用户程序不允许足够的堆栈空间。第二,如果内核将数据保留在用户空间,然后从系统调用返回,那么恶意的用户可能使用这些数据找出某些关于其它进程的信息。 5.一个计算机系统的内存有足够的空间容纳5个程序。这些程序有一半的时间处于等待I/O的空闲状态。请问CPU时间浪费的比例是多少 A:^5 =%

java实验15 多线程2 - 答案

实验十五多线程(二) 一、实验时间:姓名:学号: 二、实验目的 1、掌握线程之间的通信; 2、理解线程的生命周期; 三、知识点 1、wait()方法; 2、notify()方法; 3、notifyAll()方法; 4、线程状态转换图; 四、实验内容与步骤 1、根据线程的生命周期,画出线程的状态转换图。 2、编写程序实现以下功能:包括两个进程,分别是生产者进程和消费者进程。生产者进程依次提供:(面条,筷子)或(牛排,叉子),要求生产者每次提供食品和餐具后,要告知消费者取走,而消费者取走食品和餐具后,也要告知生产者已经取走,可以继续提供食品和餐具。public class Q { private String food; private String tool; boolean bFull=false; public synchronized void put(String food,String tool){ if(bFull) try{ wait(); } catch(Exception e){ System.out.println(e.getMessage());

} this.food=food; this.tool=tool; bFull=true; notify(); } public synchronized void get(){ if(!bFull){ try{ wait(); } catch(Exception e){ System.out.println(e.getMessage()); } } System.out.println(food+"---->"+tool); bFull=false; notify(); } } 生产者: public class Producer implements Runnable{ Q q; public Producer(Q q){ this.q=q; } public void run(){ int i=0; while(true){ if(i==0) q.put("面条", "筷子"); else q.put("牛排", "叉子"); i=(i+1)%2; } } } 消费者: public class Consumer implements Runnable { Q q; public Consumer(Q q){ this.q=q; } public void run() { while(true){

线程、进程、多线程、多进程和多任务之间的区别与联系

线程、进程、多线程、多进程和多任务之间的区别与联系

可能学习操作系统开发的读者都听说过这些专业名词,但又多少人理解了? 首先,从定义开始,先看一下教科书上进程和线程定义:进程:资源分配的最小单位。线程:程序执行的最小单位。 1 进程进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。 举例说明进程:想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕,他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法)计算机科学家就是处理器(CPU),而做蛋糕的各种原料就是输入数据。 进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。现在假设计算机科学家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这里,我们看到处理机制是从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学

家又回来做蛋糕,从他离开时的那一步继续做下去。 2 线程线程是CPU调度的最小单位(程序执行流的最小单元),它被包含在进程之中,是进程中的实际运作单元。一条线程是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一个标准的线程有线程ID、当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单元,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现处间断性。 线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。举例说明线程:假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西——-文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。这种机制就是线程。 总的来说:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。

第02章 进程与线程习题解答

第2章进程与线程 习题2 参考解答 1. 简要回答下列问题。 1) 进程和线程有什么区别? 2) 线程是如何创建的?怎样设置线程的优先级? 3) 前台线程和后台线程有什么区别?如何将一个线程设置为后台线程? 【解答】 1) 一个完整的进程拥有自己独立的内存空间和数据,但是同一个进程内的线程是共享内存空间和数据的。一个进程对应着一段程序,它是由一些在同一个程序里面独立的同时运行的线程组成的。线程有时也被称为并行运行在程序里的轻量级进程,这是因为它的运行依赖于进程提供的上下文环境,并且使用的是进程的资源。 在一个进程里,线程的调度有抢占式或者非抢占的模式。在抢占模式下,操作系统负责分配CPU时间给各个线程,一旦当前的线程使用完分配给自己的CPU时间,操作系统将决定下一个占用CPU时间的是哪一个线程。因此操作系统将定期的中断当前正在执行的线程,将CPU 分配给在等待队列的下一个线程。所以任何一个线程都不能独占CPU。每个线程占用CPU的时间取决于进程和操作系统。进程分配给每个线程的时间很短,以至于我们感觉所有的线程是同时执行的。 2) C#中创建线程的工作是通过使用System.Threading名称空间下的Thread类的构造方法来完成的,如创建一个线程实例输出字符“a”1000次。 Thread thread = new Thread(new ThreadStart(func1)); thread.Priority =ThreadPriority.Normal; thread.Start(); static void func1() { for(int i =0;i<1000;i++) { Console.WriteLine("a"); } } C#中System.Threading名称空间下的ThreadPriority枚举类定义了线程可能具有的所有优先级的值,优先级由高到低排序为:Highest,AboveNormal,Normal,BelowNormal,Lowest。可以通过访问线程的Priority属性来获取和设置其优先级。每个线程都具有分配给它的线程优先级。在公共语言运行库中创建的线程最初分配的优先级为ThreadPriority.Normal。在运行库以外创建的线程保留它们在进入托管环境之前具有的优先级。可以使用Thread.Priority属性获取或设置任何线程的优先级。 3) 前台线程和后台线程的区别是后台线程不会影响进程终止。属于某个进程的所有前台线程都终止后,公共语言运行库就会结束该进程,而且所有属于该进程的后台线程也都会立即停止,而不管后台工作是否完成。 1

单片机系统中的多任务多线程机制的实现

单片机系统中的多任务多线程机制的实现 单片机系统中的多任务多线程机制的实现 单片机系统中的多任务多线程机制的实现 2007-01-20 电子通信论文 单片机系统中的多任务多线程机制的实现 摘要:单片机系统的开发多情况下不是在嵌入式操作系统平台上进行的,而是直接基于处理器编写。在多任务并行执行的要求下,可以借鉴操作系统中的任务和线程机制,对资源和处理器合理进行调度。本文以实例对此进行讨论。关键词:单片机任务线程并行处理引言首先要指出的是一点是,我们不是讨论嵌入式实时多任务操作系统(RTOS)的设计。我们讨论的是,在不使用RTOS的控制系统中,如何体现多任务多线程机制的程序设计思想。一些嵌入式设备可以需要操作系统,例如掌上电脑、PDA、网络控制器等高性能的手持设备和移动设备。它们往往和无线通信、互联网访问和多媒体处理等复杂而强大的功能联系在一起;对CPU要求也很高,往往是以通用CPU为原型的各种高端嵌入式处理器。作为一个完整的操作系统,RTOS有一个可靠性很高的实时内核,将CPU时间、中断、I/O、定时器等资源都包括起来,留给用户一个标准的应用程序接口(API);根据各个任务的优先级,合理地在不同任务之间分配CPU的时间,保证程序执行的实时性、可靠性。内核一般都能提供任务调度和中断服务等功能,部分高档商业化产品,如WindowsXPEmbedded,甚至支持32位地址空间、虚拟存储管理、多进程以及嵌入式操作系统中不多见的动态链接库(DLL)。对于这些RTOS来说,多任务实时处理不是件困难的事情。但更多的情况下,用户使用的是另一类CPU——微控制器,即单片机,往往是按照某一流程执行单一任务。出于成本和技术上的原因,这类软件开发多数还是基于处理器直接编写,没有选配实时多任

进程管理习题

第二章进程管理 2.1.7 一、单项选择题 1.一个进程是()。 A.由协处理器执行的一个程序 B.一个独立的程序+数据集 C.PCB结构与程序和数据的组合 D.一个独立的程序 2.下列关于线程的叙述中,正确的是()。 A.线程包含CPU现场,可以独立执行程序 B.每个线程有自己独立的地址空间 C.进程只能包含一个线程 D.线程之间的通信必须使用系统调用函数 3.进程之间交换数据不能通过()途径进行。A.共享文件B.消息传递C.访问进程地址空间D.访问共享存储区4.进程和程序的根本区别是()。 A.静态和动态特点 B.是不是被调入到内存在 C.是不是具有就绪.运行和等待三种状态

D.是不是占有处理器 5.下面的叙述中,正确的是()。 A.进程获得处理器运行时通过调度得到的 B.优先级是进程调度的重要依据,一旦确定不能改动 C.在单处理器系统中,任何时刻都只有一个进程处于运行状态D.进程申请处理器而得不到满足时其状态变为阻塞状态 6.若某一进程拥有100个线程,这些线程都属于用户级线程,则在系统调度执行时间上占用的时间片是()。 A.1 B.100 C.1/100 D.0 7.进程Pl .P2和P3单独执行时间分别为10min.15 min和20min,其中处理器占用时间分别为2min.3 min和12min。如果采用多道程序设计技术使其并发,加上系统开销5min ,那么并发使得计算机系统的效率提高了()。 A.1 B.38% C.74% D.51% 8.操作系统是根据()来对并发执行的进程进行控制和管理的。A.进程的基本状态B.进程控制块 C.多道程序设计D.进程的优先权 9.在任何时刻,一个进程的状态变化()引起另一个进程的状态变化。 A.必定B.一定不C.不一定D.不可能10.在单处理器系统中,如果同时存在10个进程,则处于就绪队列中的进程最多有()个。

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。 4. 我们如何创建一个线程? 有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。若想了解更多可以阅读这篇关于如何在Java 中创建线程的文章。 5. 有哪些不同的线程生命周期?

相关文档
相关文档 最新文档