当前位置:文档之家› 线程知识

线程知识

相关链接:123456789101112131 415161718192021222324252627282930

CNBIE BLOG

线程本地存储(ThreadLocalStorage,TLS)分析与使用

原文:线程本地存储(ThreadLocalStorage,TLS)分析与使用

线程本地存储

CNBIE BLOG

线程并发包util.concurrent的研究(二)

原文:线程并发包util.concurrent的研究(二)

摘自java/j-jtp11234/">https://www.doczj.com/doc/1e4742443.html,/developerWorks/cn/java/j-jtp11234/

1.新原子类诞生之前的黑暗时代:

在 JSDK 1.5 之前,如果不使用本机代码,就不能用Java语言编写无等待、无锁定的算法。在java.util.concurrent 中添加原子变量类之后,这种情况发生了变化。请跟随并行专家 Brian Goetz 一起,了解这些新类如何使用Java语言开发高度可伸缩的无阻塞算法。您可以在本文的java s cript:void forumWindow()"

xmlns:xsi="https://www.doczj.com/doc/1e4742443.html,/2001/XMLSchema-instance">论坛中与作者或其他读者共享您对本文的看法。(也可以通过单击文章顶部或者底部的讨论链接来访问讨论。)

十五年前,多处理器系统是高度专用系统,要花费数十万美元(大多数具有两个到四个处理器)。现在,多处理器系统很便宜,而且数量很多,几乎每个主要微处理器都内置了多处理支持

,其中许多系统支持数十个或数百个处理器。

要使用多处理器系统的功能,通常需要使用多线程构造应用程序。但是正如任何编写并发应用程序的人可以告诉你的那样,要获得好的硬件利用率,只是简单地在多个线程中分割工作是不够的,还必须确保线程确实大部分时间都在工作,而不是在等待更多的工作,或等待锁定共享数据结构。

问题:线程之间的协调

如果线程之间不需要协调,那么几乎没有任务可以真正地并行。以线程池为例,其中执行的任务通常相互独立。如果线程池利用公共工作队列,则从工作队列中删除元素或向工作队列添加元素的过程必须是线程安全的,并且这意味着要协调对头、尾或节点间链接指针所进行的访问。正是这种协调导致了所有问题。

标准方法:锁定

在Java语言中,协调对共享字段的访问的传统方法是使用同步,确保完成对共享字段的所有访问,同时具有适当的锁定。通过同步,可以确定(假设类编写正确)具有保护一组给定变量的锁定的所有线程都将拥有对这些变量的独占访问权,并且以后其他线程获得该锁定时,将可以看到对这些变量进行的更改。弊端是如果锁定竞争太厉害(线程常常在其他线程具有锁定时要求获得该锁定),会损害吞吐量,因为竞争的同步非常昂贵。(Public Service Announcement:对于现代 JVM 而言,无竞争的同步现在非常便宜。

基于锁定的算法的另一个问题是:如果延迟具有锁定的线程(因为页面错误、计划延迟或其他意料之外的延迟),则没有要求获得该锁定的线程可以继续运行。

还可以使用可变变量来以比同步更低的成本存储共享变量,但它们有局限性。虽然可以保证其他变量可以立即看到对可变变量的写入,但无法呈现原子操作的读-修改-写顺序,这意味着(比如说)可变变量无法用来可靠地实现互斥(互斥锁定)或计数器。

使用锁定实现计数器和互斥

假如开发线程安全的计数器类,那么这将暴露get()、increment()和decrement()操作。清单 1 显示了如何使用锁定(同步)实现该类的例子。注意所有方法,甚至需要同步get(),使类成为线程安全的类,从而确保没有任何更新信息丢失,所有线程都看到计数器的最新值。

清单 1. 同步的计数器类

increment()和decrement()操作是原子的读-修改-写操作,为了安全实现计数器,必须使用当前值,并为其添加一个值,或写出新值,所有这些均视为一项操作,其他线程不能打断它。否则,如果两个线程试图同时执行增加,操作的不幸交叉将导致计数器只被实现了一次,而不是被实现两次。(注意,通过使值实例变量成为可变变量并不能可靠地完成这项操作。)

许多并发算法中都显示了原子的读-修改-写组合。清单 2 中的代码实现了简单的互斥,acquire()方法也是原子的读-修改-写操作。要获得互斥,必须确保没有其他人具有该互斥(curOwner = Thread.currentThread()),然后记录您拥有该互斥的事实(curOwner = Thread.currentThread()),所有这些使其他线程不可能在中间出现以及修改curOwner field。

清单 2. 同步的互斥类

清单 1 中的计数器类可以可靠地工作,在竞争很小或没有竞争时都可以很好地执行。然而,在竞争激烈时,这将大大损害性能,因为 JVM 用了更多的时间来调度线程,管理竞争和等待线程队列,而实际工作(如增加计数器)的时间却很少。您可以回想java/library/j-jtp10264/" xmlns:xsi="https://www.doczj.com/doc/1e4742443.html,/2001/XMLSchema-instance">上月专栏中的图,该图显示了一旦多个线程使用同步竞争一个内置监视器,吞吐量将如何大幅度下降。虽然该专栏说明了新的ReentrantLock类如何可以更可伸缩地替代同步,但是对于一些问题,还有更好的解决方法。

锁定问题

使用锁定,如果一个线程试图获取其他线程已经具有的锁定,那么该线程将被阻塞,直到该锁定可用。此方法具有一些明显的缺点,其中包括当线程被阻塞来等待锁定时,它无法进行其他任何操作。如果阻塞的线程是高优先级的任务,那么该方案可能造成非常不好的结果(称为优先级倒置的危险)。

使用锁定还有一些其他危险,如死锁(当以不一致的顺序获得多个锁定时会发生死锁)。甚至没有这种危险,锁定也仅是相对的粗粒度协调机制,同样非常适合管理简单操作,如增加计数器或更新互斥拥有者。如果有更细粒度的机制来可靠管理对单独变量的并发更新,则会更好一些;在大多数现代处理器都有这种机制。

2.伟大的发明-新原子类:

硬件同步原语

如前所述,大多数现代处理器都包含对多处理的支持。当然这种支持包括多处理器可以共享外部设备和主内存,同时它通常还包括对指令系统的增加来支持多处理的特殊要求。特别是,几乎每个现代处理器都有通过可以检测或阻止其他处理器的并发访问的方式来更新共享变量的指令。

比较并交换 (CAS)

支持并发的第一个处理器提供原子的测试并设置操作,通常在单位上运行这项操作。现在的处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为比较并转换或 CAS 的原语。(在 Intel 处理器中,比较并交换通过指令的 cmpxchg 系列实现。PowerPC 处理器有一对名为“加载并保留”和“条件存储”的指令,它们实现相同的目地;MIPS 与 PowerPC 处理器相似,除了第一个指令称为“加载链接”。)

CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。清单 3 说明了 CAS 操作的行为(而不是性能特征),但是 CAS 的价值是它可以在硬件中实现,并且是极轻量级的(在大多数处理器中):

清单 3. 说明比较并交换的行为(而不是性能)的代码

使用 CAS 实现计数器

基于 CAS 的并发算法称为无锁定算法,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。清单 4 显示了重新编写的计数器类来使用 CAS 替代锁定:

清单 4. 使用比较并交换实现计数器

无锁定且无等待算法

如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是无等待的。与此形成对比的是,无锁定算法要求仅某个线程总是执行操作。(无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作,而不管其他线程的操作、计时、交叉或速度。这一限制可以是系统中线程数的函数;例如,如果有 10 个线程,每个线程都执行一次CasCounter.increment()操作,最坏的情况下,每个线程将必须重试最多九次,才能完成增加。)

再过去的 15 年里,人们已经对无等待且无锁定算法(也称为无阻塞算法)进行了大量研究,许多人通用数据结构已经发现了无阻塞算法。无阻塞算法被广泛用于操作系统和 JVM 级别,进行诸如线程和进程调度等任务。虽然它们的实现比较复杂,但相对于基于锁定的备选算法,它们有许多优点:可以避免优先级倒置和死锁等危险,竞争比较便宜,协调发生在更细的粒度级别,允许更高程度的并行机制等等。

原子变量类

在 JDK 5.0 之前,如果不使用本机代码,就不能用Java语言编写无等待、无锁定的算法。在

java.util.concurrent.atomic包中添加原子变量类之后,这种情况才发生了改变。所有原子变量类都公开比较并设置原语(与比较并交换类似),这些原语都是使用平台上可用的最快本机结构(比较并交换、加载链接/条件存储,最

坏的情况下是旋转锁)来实现的。java.util.concurrent.atomic包中提供了原子变量的 9 种风格(AtomicInteger;AtomicLong;AtomicReference;AtomicBoolean;原子整型;长型;引用;及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)。

原子变量类可以认为是volatile变量的泛化,它扩展了可变变量的概念,来支持原子条件的比较并设置更新。读取和写入原子变量与读取和写入对可变变量的访问具有相同的存取语义。

虽然原子变量类表面看起来与清单 1 中的SynchronizedCounter例子一样,但相似仅是表面的。在表面之下,原子变量的操作会变为平台提供的用于并发访问的硬件原语,比如比较并交换。

更细粒度意味着更轻量级

调整具有竞争的并发应用程序的可伸缩性的通用技术是降低使用的锁定对象的粒度,希望更多的锁定请求从竞争变为不竞争。从锁定转换为原子变量可以获得相同的结果,通过切换为更细粒度的协调机制,竞争的操作就更少,从而提高了吞吐量。

3.JSDK1.4中如何使用新原子类:

由于本人没有安装jsdk1.5,所以没有办法享受util.concurrent中的快乐。不过可以使用支持jsdk1.4的兼容包-Java 1.4 backport of JSR 166 (java.util.concurrent),这个包是由Dawid Kurzyniec先生维护。可访问

https://www.doczj.com/doc/1e4742443.html,/dcl/util/backport-util-concurrent/来获取详细信息。

此包中实现了jsdk1.5的util.concurrent所有重要的特性,但是在一些某些方面还是有所欠缺。至于其原

因就不言自明了,JVM1.4支持的不够。

以下为目前Java 1.4 backport of JSR 166所支持的特性:

All JSR 166 executors, utilities, and everything related (thread pools, FutureTask, scheduled tasks and executors, etc)

Locks: ReentrantLock, Semaphore, ReentrantReadWriteLock (see remarks below), Conditions

Queues: synchronous, array, linked, delay, and priority queues

Atomics: everything except reflection-based updaters

Other concurrency utils: CountDownLatch, CyclicBarrier

Collections: ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet

下面为目前Java 1.4 backport of JSR 166所不支持的特性:

In Condition

Method long awaitNanos(long nanosTimeout) is not supported, since the emulation cannot reliably report remaining times with nanosecond precision. Thus, it propably would be too dangerous to leave the emulated method in the Condition interface. However, the method is still available, for those who know what they are doing, in the

util.concurrent.helpers.Utils class.

Methods boolean await(timeout) and boolean awaitUntil(Date), called on conditions obtained from locks, may sometimes wake up spuriously. This is allowed by the Condition specification. However, it causes them to occassionally fail TCK unit tests. It has been acknowledged by Doug Lea, JSR 166 expert group lead, that these tests are too rigorous; they will be relaxed in future releases. The bottom line is that this implementation does conform to the specification in that respect.

In ReentrantLock

The following monitoring methods are not supported: boolean hasWaiters(Condition), int

getWaitQueueLength(Condition), Collection getWaitingThreads(Condition).

the following monitoring methods are supported only for fair locks: boolean hasQueuedThreads(), int getQueueLength(), Collection getQueuedThreads(), boolean isQueued().

In ReentrantReadWriteLock

The current backport implementation is based on dl.util.concurrent class ReentrantWriterPreferenceReadWriteLock, and thus slightly departs from java.util.concurrent that does not specify acquisition order but allows to enable/disable fairness. The backport implementation does not have a single-parameter constructor allowing to specify fairness policy; it always behaves like writer-preference lock with no fairness guarantees. Bacause of these characteristics, this class is compliant with JSR 166 specification of non-fair reentrant read-write locks, while the exact semantics of fair locks are not supported (and the appropriate constructor is missing).

Also, the following instrumentation and status methods are not supported: Collection getQueuedWriterThreads(), Collection getQueuedReaderThreads(), boolean hasQueuedThreads(), boolean hasQueuedThread(Thread), Collection getQueuedThreads(), boolean hasWaiters(Condition), int getWaitQueueLength(Condition), Collection getWaitingThreads(Condition).

In Semaphore

Atomic multi-acquires: tryAcquire(int permits) and tryAcquire(int permits, long timeout, TimeUnit unit) are not supported.

Platform-level functionality

To emulate System.nanoTime(), the method nanoTime() is provided in the class dl.util.concurrent.helpers.Utils. On Java1.4.2, it attempts to use high-precision timer via sun.misc.Perf (thanks to Craig Mattocks for suggesting this). On older Java platforms, or when sun.misc.Perf is not supported, it falls back to System.currentTimeMillis().

Class ThreadHelpers (added in 1.0_01) is provided to emulate certain asp ects of Thread.UncaughtExceptionHandler.

Note on nanosecond precision timing

The backport strives to honor nanosecond timeouts, if such are requested, by using two-parameter variant of Object.wait(). Note, however, that most Java platforms before 5.0 will round up the timeout to full milliseconds anyway.

Low-level concurrency classes

The following classes are not supported: LockSupport, AbstractQueuedSynchronizer.

Atomic utilities

The following "atomic" utilities are not supported: Atomic[Integer,Long,Reference]FieldUpdater.

Collections

The following collection classes are not supported: LinkedList, ConcurrentLinkedQueue.

总的来说基本可以满足我的要求了。

asp?id=41523" width="1" height="1">

CNBIE BLOG

线程池跟自定义事件的应用例子

原文:线程池跟自定义事件的应用例子

using System;

using System.Threading;

using https://www.doczj.com/doc/1e4742443.html,;

using System.Text;

using System.Text.RegularExpressions;

using System.IO;

namespace ThreadPoolDelegate

{

//public delegate void ReceiveCompletedEventHandler(string url,string title,string sourcecode);

public delegate void ReceiveCompletedEventHandler(object sender,ReceiveCompletedEventArgs e);

///

/// Class1 的摘要说明。

///

public class Class1

{

public event ReceiveCompletedEventHandler ReceiveCompletedEvent;

public Class1()

{

//

// TODO: 在此处添加构造函数逻辑

//

}

public void ReadSourceCode(object o)

{

string url = (string)o;

string title = "";

string sourcecode = "";

WebClient wc = new WebClient();

byte[] data = wc.DownloadData(url);

sourcecode = Encoding.Default.GetString(data);

title = this.GetPageTitle(sourcecode);

if (ReceiveCompletedEvent != null)

{

//ReceiveCompletedEvent(url,title,sourcecode);

ReceiveCompletedEvent(this,new

ReceiveCompletedEventArgs(url,title,sourcecode));

}

}

public void ReadSourceCode(object o,bool timeout)

{

string url = (string)o;

string title = "";

string sourcecode = "";

WebClient wc = new WebClient();

byte[] data = wc.DownloadData(url);

sourcecode = Encoding.Default.GetString(data);

title = this.GetPageTitle(sourcecode);

if (ReceiveCompletedEvent != null)

{

//ReceiveCompletedEvent(url,title,sourcecode);

ReceiveCompletedEvent(this,new

ReceiveCompletedEventArgs(url,title,sourcecode));

}

}

private string GetPageTitle(string str)

{

string lTitle = "";

string lPattern = "(?:<\\s*title\\s*>(?[^<]+))";

Regex rx = new Regex(lPattern, RegexOptions.IgnoreCase | https://www.doczj.com/doc/1e4742443.html,piled ); Match mt = rx.Match(str);

if ( mt.Success )

try

{

lTitle = mt.Groups["t"].Value.ToString();

}

catch

{

lTitle ="";

}

else

lTitle = "";

return lTitle;

}

}

public class test

{

static ManualResetEvent ev = new ManualResetEvent(false);

private static int intCount;

private static DateTime dtStart = DateTime.Now;

public static void Main(string[] args)

{

Class1 c = new Class1();

c.ReceiveCompletedEvent += new ReceiveCompletedEventHandler(WriteOut);

if (args.Length >=1)

{

for (int i=0;i

{

//ThreadPool.QueueUserWorkItem(new

WaitCallback(c.ReadSourceCode),args[i]);

ThreadPool.RegisterWaitForSingleObject(ev,new WaitOrTimerCallback(c.ReadSourceCode),args[i],3000,true);

intCount++;

}

ev.WaitOne();

}

else

{

Console.WriteLine("you must pass an argument at least ");

}

}

private static void WriteOut(object sender,ReceiveCompletedEventArgs e)

{

{

intCount--;

Console.WriteLine("正在执行......{0}",e.URL);

FileStream fs = new

FileStream(@"c:\"+e.Title+".txt",FileMode.Create,FileAccess.Write);

StreamWriter sw = new StreamWriter(fs);

sw.WriteLine(e.URL);

sw.WriteLine();

sw.WriteLine(e.Title);

sw.WriteLine();

sw.WriteLine(e.SourceCode);

sw.WriteLine();

sw.Flush();

fs.Flush();

sw.Close();

fs.Close();

Console.WriteLine("执行完成......{0}",e.URL);

if(intCount == 0)

{

Console.WriteLine("程序执行完成.....");

TimeSpan tsSpent = DateTime.Now.Subtract(dtStart);

Console.WriteLine("程序执行时间......{0}毫秒",tsSpent.TotalMilliseconds); Console.WriteLine("程序在退出之前等待5秒,only for demo");

Thread.Sleep(5000);

ev.Set();

}

}

catch(Exception ex)

throw new Exception(ex.Message);

}

}

/*

private static void WriteOut(string url,string title,string sourcecode)

{

intCount--;

FileStream fs = new

FileStream(@"c:\"+title+".txt",FileMode.Create,FileAccess.Write);

StreamWriter sw = new StreamWriter(fs);

sw.WriteLine(url);

sw.WriteLine();

sw.WriteLine(title);

sw.WriteLine();

sw.WriteLine(sourcecode);

sw.WriteLine();

sw.Flush();

fs.Flush();

sw.Close();

fs.Close();

Console.WriteLine("执行完成......{0}",e.URL);

if(intCount == 0)

{

Console.WriteLine("程序执行完成.....");

TimeSpan tsSpent = DateTime.Now.Subtract(dtStart);

Console.WriteLine("程序执行时间......{0}毫秒",tsSpent.TotalMilliseconds); Console.WriteLine("程序在退出之前等待10秒,only for demo");

Thread.Sleep(10000);

ev.Set();

}

}

*/

}

public class ReceiveCompletedEventArgs : System.EventArgs

{

string _url;

string _title;

string _sourcecode;

public string URL

{

get

{

return _url;

}

}

public string Title

{

get

{

return _title;

}

}

public string SourceCode

{

get

{

return _sourcecode;

}

}

public ReceiveCompletedEventArgs(string url,string title,string sourcecode) {

this._url = url;

this._title = title;

this._sourcecode = sourcecode;

}

}

}

这里定义了两种委托的方式,大家随便看看

CNBIE BLOG

线程处理

原文:线程处理

线程处理

作者:高山

Visual https://www.doczj.com/doc/1e4742443.html,允许用户开发多条互不相干的多线程的应用程序。也就是说,当用户在执行一个程序的时候,也可以在另外一条的线程独立地运行另外一个任务程序,这种过程被称为自由线程(Free Threading),对于用户而言,自由线程概念的引入,使得应用程序对用户的响应将更加积极,因为任务处理器能够在某一个线程正在运行时,依然保持界面对用户的响应,只要对用户响应的线程正在运行。而且,自由线程在运行大型的程序的时候,也将回很有用,因为随着运行任务的增加,用户可以多开辟几个线程来运行程序。

1.1.1何时使用线程

需要用户交互的软件必须尽可能快地对用户的活动作出反应,以便提供丰富多彩的用户体验。但同时它必须执行必要的计算以便尽可能快地将数据呈现给用户。如果应用程序仅使用一个执行线程,则可以结合使用异步编程与.NET 远程处理或使用https://www.doczj.com/doc/1e4742443.html, 创建的

asp netbuildingwebservices asp netwebserviceclients.htm">XML Web services,以便在使用自己计算机的处理时间以外再使用其他计算机的处理时间,从而提高对用户的响应速度并减少应用程序的数据处理时间。如果您正在进行大量的输入/输出工作,则还可以使用 I/O 完成端口来提高应用程序的响应速度。

1.1.2 多个线程的优点

无论如何,要提高对用户的响应速度并且处理所需数据以便几乎同时完成工作,使用多个线程是一种最为强大的技术。在具有一个处理器的计算机上,多个

线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。例如,在另一个线程正在重新计算同一应用程序中的电子表格的其他部分时,用户可以编辑该电子表格。

无需修改,同一个应用程序在具有多个处理器的计算机上运行时将极大地满足用户的需要。单个应用程序域可以使用多个线程来完成以下任务:

●通过https://www.doczj.com/doc/1e4742443.html,/ class=wordstyle>网络与 Web 服务器和数据库进行通讯。

●执行占用大量时间的操作。

●区分具有不同优先级的任务。例如,高优先级线程管理时间关键的任务,

低优先级线程执行其他任务。

●使用户界面可以在将时间分配给后台任务时仍能快速作出响应。

1.1.3 多个线程的缺点

建议您使用尽可能少的线程,这样可以使操作系统资源的使用率最低,并可提高性能。线程处理还具有在设计应用程序时要考虑的资源要求和潜在冲突。这些资源要求如下所述:

●系统将为进程、AppDomain对象和线程所需的上下文信息使用内存。因

此,可以创建的进程、AppDomain对象和线程的数目会受到可用内存的限制。

●跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多

数线程都不会产生明显的进度。如果大多数当前线程处于一个进程中,则其他进程中的线程的调度频率就会很低。

●使用许多线程控制代码执行非常复杂,并可能产生许多错误。

●销毁线程需要了解可能发生的问题并对那些问题进行处理。

提供对资源的共享访问会造成冲突。为了避免冲突,必须对共享资源的访问进行同步或控制。未能正确地使访问同步(在相同或不同的应用程序域中)会导致诸如死锁(两个线程都停止响应,并且都在等待对方完成)和争用条件(由于意外地出现对两个事件的执行时间的临界依赖性而发生反常的结果)等问题。系统提供了可用于协调多个线程之间的资源共享的同步对象。减少线程的数目使同步资源更为容易。

需要同步的资源包括:

●系统资源(如通讯端口)。

●多个进程所共享的资源(如文件句柄)。

●由多个线程访问的单个应用程序域的资源(如全局、静态和实例字段)。

●Gaoshan333333@https://www.doczj.com/doc/1e4742443.html,

asp?id=17473" width="1" height="1">

CNBIE BLOG

线程的基础知识

原文:线程的基础知识

线程的基础知识

1. 进程与线程有那些区别和联系?

每个进程至少需要一个线程。

进程由两部分构成:进程内核对象,地址空间。线程也由两部分组成:线程内核对象,操作系统用它来对线程实施管理。线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。

进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。

如果在单进程环境中,有多个线程正在运行,那么这些线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。

进程使用的系统资源比线程多得多。实际上,线程只有一个内核对象和一个堆栈,保留的记录很少,因此需要很少的内存。因此始终都应该设法用增加线程来解决编程问题,避免创建新的进程。但是许多程序设计用多个进程来实现会更好些。

2. 如何使用_beginthreadex函数?

使用方法与CreateThread函数相同,只是调用参数类型需要转换。

3. 如何使用CreateThread函数?

当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。使用时应当注意在不需要对线程内核进行访问后调用CloseHandle函数关闭线程句柄。因为CreateThread函数中使用某些C/C++运行期库函数时会有内存泄漏,所以应当尽量避免使用。

参数含义:

lpThreadAttributes 如果传递NULL该线程使用默认安全属性。如果希望所有的子进程能够继承该线程对象的句柄,必须将它的bInheritHandle成员被初始化为TRUE。

dwStackSize 设定线程堆栈的地址空间。如果非0,函数将所有的存储器保留并分配给线程的堆栈。如果是0,CreateThread就保留一个区域,并且将链接程序嵌入.exe文件的/STACK链接程序开关信息指明的存储器容量分配给线程堆栈。

lpStartAddress 线程函数的地址。

lpParameter 传递给线程函数的参数。

dwCreationFlags 如果是0,线程创建后立即进行调度。如果是

CREATE_SUSPENDED,系统对它进行初始化后暂停该线程的运行。LpThreadId 用来存放系统分配给新线程的ID。

4. 如何终止线程的运行?

(1) 线程函数返回(最好使用这种方法)。

这是确保所有线程资源被正确地清除的唯一办法。

如果线程能够返回,就可以确保下列事项的实现:

在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。

操作系统将正确地释放线程堆栈使用的内存。

系统将线程的退出代码设置为线程函数的返回值。

系统将递减线程内核对象的使用计数。

(2) 调用ExitThread函数(最好不要使用这种方法)。

该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。

(3) 调用TerminateThread函数(应该避免使用这种方法)。TerminateThread能撤消任何线程。线程的内核对象的使用计数也被递减。TerminateThread函数是异步运行的函数。如果要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数。当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。

(4) 包含线程的进程终止运行(应该避免使用这种方法)。

由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。就像从每个剩余的线程调用TerminateThread一样。这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。

一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。

5. 为什么不要使用_beginthread函数和_endthread函数?

与_beginthreadex函数相比参数少,限制多。无法创建暂停的线程,无法取得线程ID。_endthread函数无参数,线程退出代码必须为0。还有

_endthread函数内部关闭了线程的句柄,一旦退出将不能正确访问线程句柄。

6. 如何对进程或线程的内核进行引用?

HANDLE GetCurrentProcess( );

HANDLE GetCurrentThread( );

这两个函数都能返回调用线程的进程的伪句柄或线程内核对象的伪句柄。伪句柄只能在当前的进程或线程中使用,在其它线程或进程将不能访问。函数并不在创建进程的句柄表中创建新句柄。调用这些函数对进程或线程内核对象的使用计数没有任何影响。如果调用CloseHandle,将伪句柄作为参数来传递,那么CloseHandle就会忽略该函数的调用并返回FALSE。

DWORD GetCurrentProcessId( );

DWORD GetCurrentThreadId( );

这两个函数使得线程能够查询它的进程的唯一ID或它自己的唯一ID。

7. 如何将伪句柄转换为实句柄?

HANDLE hProcessFalse = NULL;

HANDLE hProcessTrue = NULL;

HANDLE hThreadFalse = NULL;

HANDLE hThreadTrue = NULL;

hProcessFalse = GetCurrentProcess( );

hThreadFalse = GetCurrentThread( );

取得线程实句柄:

DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse,

&hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );

取得进程实句柄:

DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse,

&hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );

由于DuplicateHandle会递增特定对象的使用计数,因此当完成对复制对象句柄的使用时,应该将目标句柄传递给CloseHandle,从而递减对象的使用计数。

8. 在一个进程中可创建线程的最大数是得多少?

线程的最大数取决于该系统的可用虚拟内存的大小。默认每个线程最多可拥有至多1MB大小的栈的空间。所以,至多可创建2028个线程。如果减少默认堆栈的大小,则可以创建更多的线程。

线程的调度、优先级和亲缘性

1. 如何暂停和恢复线程的运行?

线程内核对象的内部有一个值指明线程的暂停计数。当调用CreateProcess或CreateThread函数时,就创建了线程的内核对象,并且它的暂停计数被初始化为1。因为线程的初始化需要时间,不能在系统做好充分的准备之前就开始执行线程。线程完全初始化好了之后,CreateProcess 或CreateThread要查看是否已经传递了CREATE_SUSPENDED标志。如果已经传递了这个标志,那么这些函数就返回,同时新线程处于暂停状态。如果尚未传递该标志,那么该函数将线程的暂停计数递减为0。当线程的暂停计数是0的时候,除非线程正在等待其他某种事情的发生,否则该线程就处于可调度状态。在暂停状态中创建一个线程,就能够在线程有机会执行任何代码之前改变线程的运行环境(如优先级)。一旦改变了线程的环境,必须使线程成为可调度线程。方法如下:

hThr ead = CreatThread( ……,CREATE_SUSPENDED,…… );

bCreate = CreatProcess( ……,CREATE_SUSPENDED,……,pProcInfo ); if( bCreate != FALSE )

{

hThread = pProcInfo.hThread;

}

……

……

……

ResumeThread( hThread );

CloseHandle( hThread );

ResumeThread成功,它将返回线程的前一个暂停计数,否则返回0xFFFFFFFF。单个线程可以暂停若干次。如果一个线程暂停了3次,它必须恢复3次。创建线程时,除了使用CREATE_SUSPENDED外,也可以调用SuspendThread函数来暂停线程的运行。任何线程都可以调用该函数来暂停另一个线程的运行(只要拥有线程的句柄)。线程可以自行暂停运行,但是不能自行恢复运行。与ResumeThread一样,SuspendThread返回的是线程的前一个暂停计数。线程暂停的最多次数可以是MAXIMUM_SUSPEND_COUNT次。SuspendThread与内核方式的执行是异步进行的,但是在线程恢复运行之前,不会发生用户方式的执行。调用SuspendThread时必须小心,因为不知道暂停线程运行时它在进行什么操作。只有确切知道目标线程是什么(或者目标线程正在做什么),并且采取强有力的措施来避免因暂停线程的运行而带来的问题或死锁状态,SuspendThread才是安全的。

2. 是否可以暂停和恢复进程的运行?

对于Windows来说,不存在暂停或恢复进程的概念,因为进程从来不会被安排获得CPU时间。不过Windows确实允许一个进程暂停另一个进程中的所有线程的运行,但是从事暂停操作的进程必须是个调试程序。特别是,进程必须调用WaitForDebugEvent和ContinueDebugEvent之类的函数。由于竞争的原因,Windows没有提供其他方法来暂停进程中所有线程的运行。

3. 如何使用sleep函数?

系统将在大约的指定毫秒数内使线程不可调度。Windows不是个实时操作系统。虽然线程可能在规定的时间被唤醒,但是它能否做到,取决于系统中还有什么操作正在进行。

可以调用Sleep,并且为dwMilliseconds参数传递INFINITE。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。可以将0传递给Sleep。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。但是,系统可以对刚刚调用Sleep的线程重新调度。如果不存在多个拥有相同优先级的可调度线程,就会出现这种情况。

4. 如何转换到另一个线程?

系统提供了SwitchToThread函数。当调用这个函数的时候,系统要查看是否存在一个迫切需要CPU时间的线程。如果没有线程迫切需要CPU时间,SwitchToThread就会立即返回。如果存在一个迫切需要CPU时间的线程,SwitchToThread就对该线程进行调度(该线程的优先级可能低于调用SwitchToThread的线程)。这个迫切需要CPU时间的线程可以运行一个时间段,然后系统调度程序照常运行。该函数允许一个需要资源的线程强制另一个优先级较低、而目前却拥有该资源的线程放弃该资源。如果调用SwitchToThread函数时没有其他线程能够运行,那么该函数返回FALSE,否则返回一个非0值。调用SwitchToThread与调用Sleep是相似的。差别是SwitchToThread允许优先级较低的线程运行;而即使有低优先级线程迫切需

要CPU时间,Sleep也能够立即对调用线程重新进行调度。

5. 如何取得线程运行的时间?

(1) 简单取得线程大概运行时间:

DWORD dwStartTime = 0;

DWORD dwEndTime = 0;

DWORD dwRunTime = 0;

dwStartTime = GetTickCount( );

……

……

……

dwEndTime = GetTickCount( );

dwRunTime = dwEndTime – dwStartTime;

(2) 调用GetThreadTimes的函数:

参数含义:

hThread 线程句柄

lpCreationTime 创建时间:英国格林威治时间

lpExitTime 退出时间:英国格林威治时间,如果线程仍然在运行,退出时间则未定义

lpKernelTime 内核时间:指明线程执行操作系统代码已经经过了多少个100ns的CPU时间

lpUserTime 用户时间:指明线程执行应用程序代码已经经过了多少个100ns 的CPU时间

GetProcessTimes是个类似GetThreadTimes的函数,适用于进程中的所有线程(甚至是已经终止运行的线程)。返回的内核时间是所有进程的线程在内核代码中经过的全部时间的总和。GetThreadTimes和GetProcessTimes这两个函数在Windows98中不起作用。在Windows98中,没有一个可靠的机制可供应用程序来确定线程或进程已经使用了多少CPU时间。

6. 进程的优先级类有哪些?

优先级类标识符描述

实时 REALTIME_PRIORITY_CLASS 立即对事件作出响应,执行关键时间的任务。会抢先于操作系统组件之前运行。

高 HIGH_PRIORITY_CLASS 立即对事件作出响应,执行关键时间的任务。高于正常 ABOVE_NORMAL_PRIORITY_CLASS 在正常优先级与高优先级之间运行(Windows2000)。

正常 NORMAL_PRIORITY_CLASS 没有特殊调度需求

低于正常 BELOW_NORMAL_PRIORITY_CLASS 在正常优先级与空闲优先级之间运行(Windows2000)。

空闲 IDLE_PRIORITY_CLASS 在系统空闲时运行。

设置方法:

BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriority ); DWORD GetPriorityClass( HANDLE hProcess );

使用命令外壳启动一个程序时,该程序的起始优先级是正常优先级。如果使

用Start命令来启动该程序,可以使用一个开关来设定应用程序的起始优先级。例如:

c:\>START /LOW CALC.EXE

Start命令还能识别/BELOWNORMAL、/NORMAL、/ABOVENORMAL、/HIGH和

/REALTIME等开关。

7. 线程的相对优先级有哪些?

相对优先级标识符描述

关键时间 THREAD_PRIORITY_TIME_CRITICAL 对于实时优先级类线程在优先级31上运行,对于其他优先级类,线程在优先级15上运行。

最高 THREAD_PRIORITY_HIGHEST 线程在高于正常优先级上两级上运行。高于正常 THREAD_PRIORITY_ABOVE_NORMAL 线程在正常优先级上一级上运行。

正常 THREAD_PRIORITY_NORMAL 线程在进程的优先级类上正常运行。

低于正常 THREAD_PRIORITY_BELOW_NORMAL 线程在低于正常优先级下一级上运行。

最低 THREAD_PRIORITY_LOWEST 线程在低于正常优先级下两级上运行。空闲 THREAD_PRIORITY_IDLE 对于实时优先级类线程在优先级16上运行对于其他优先级类线程在优先级1上运行。

设置方法:

BOOL SetThreadPriority( HANDLE hThread, DWORD dwPriority ); DWORD GetThreadPriorityClass( HANDLE hThread );

8. 如何避免系统动态提高线程的优先级等级?

系统常常要提高线程的优先级等级,以便对窗口消息或读取磁盘等I/O事件作出响应。或者当系统发现一个线程在大约3至4s内一直渴望得到CPU时间,它就将这个渴望得到CPU时间的线程的优先级动态提高到15,并让该线程运行两倍于它的时间量。当到了两倍时间量的时候,该线程的优先级立即返回到它的基本优先级。下面的函数可以对系统的调度方式进行设置:BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL bDisableBoost ); BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pbDisableBoost );

BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL bDisableBoost ); BOOL GetThreadPriorityBoost( HANDLE hThread, PBOOL pbDisableBoost ); SetProcessPriorityBoost负责告诉系统激活或停用进行中的所有线程的优先级提高功能,而SetThreadPriorityBoost则激活或停用各个线程的优先级提高功能。Windows98没有提供这4个函数的有用的实现代码。

用户方式中线程的同步

1. 仅一条语句用不用考虑线程同步的问题?

当使用高级语言编程时,我们往往会认为一条语句是最小的原子访问,CPU 不会在这条语句中间运行其他的线程。这是错误的,因为即使非常简单的一条高级语言的语句,经编译器编译后也可能变成多行代码由计算机来执行。因此必须考虑线程同步的问题。任何线程都不应该通过调用简单的C语句来

相关主题
相关文档 最新文档