thread

thread

计算机科学术语
线程(thread, 台湾称执行绪)是"进程"中某个单一顺序的控制流。也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。
    中文名:线程 外文名:thread 所属学科:计算机

概念

一般来说,我们把正在计算机中执行的程序叫做“进程”(process) ,而不将其称为“程序”(program)。所谓“线程”(thread),是“进程”中某个单一顺序的控制流。新兴的操作系统,如Mac、Windows NT、Windows 95等,大多采用多线程的概念,把线程视为基本执行单位。

甚至最简单的Applet也是由多个线程来完成的。在Java中,任何一个Applet的paint()和update()方法都是由AWT(Abstract Window Toolkit)绘图与事件处理线程调用的,而Applet 主要的里程碑方法——init(),start(),stop()和destory()——是由执行该Applet的应用调用的。

单线程的概念没有什么新的地方,真正有趣的是在一个程序中同时使用多个线程来完成不同的任务。某些地方用轻量进程(Lightweig ht Process)来代替线程,线程与真正进程的相似性在于它们都是单一顺序控制流。然而线程被认为轻量是由于它运行于整个程序的上下文内,能使用整个程序共有的资源和程序环境。

作为单一顺序控制流,在运行的程序内线程必须拥有一些资源作为必要的开销。例如,必须有执行堆栈和程序计数器。在线程内执行的代码只在它的上下文中起作用,因此某些地方用"执行上下文"来代替"线程"。

属性

为了正确有效地使用线程,必须理解线程的各个方面并了解Java 实时系统。必须知道如何提供线程体、线程的生命周期、实时系统如 何调度线程、线程组、什么是幽灵线程(Demo nThread)。

(1)线程体

所有的操作都发生在线程体中,在Java中线程体是从Thread类继承的run()方法,或实现Runnable接口的类中的run()方法。当线程产生并初始化后,实时系统调用它的run()方法。run()方法内的代码实现所产生线程的行为,它是线程的主要部分。

(2)线程状态

附图表示了线程在它的生命周期内的任何时刻所能处的状态以及引起状态改变的方法。这图并不是完整的有限状态图,但基本概括了线程中比较感兴趣和普遍的方面。以下讨论有关线程生命周期以此为据。

新线程态(New Thread)

可运行态(Runnable)

非运行态(Not Runnable)

死亡态(Dead)

(3)线性优先级

虽然我们说线程是并发运行的。然而事实常常并非如此。正如前面谈到的,当系统中只有一个CPU时,以某种顺序在单CPU情况下执行多线程被称为调度(scheduling)。Java采用的是一种简单、固定的调度法,即固定优先级调度。这种算法是根据处于可运行态线程的相对优先级来实行调度。当线程产生时,它继承原线程的优先级。在需要时可对优先级进行修改。在任何时刻,如果有多条线程等待运行,系统选择优先级最高的可运行线程运行。只有当它停止、自动放弃、或由于某种原因成为非运行态低优先级的线程才能运行。如果两个线程具有相同的优先级,它们将被交替地运行。

Java实时系统的线程调度算法还是强制性的,在任何时刻,如果一个比其他线程优先级都高的线程的状态变为可运行态,实时系统将选择该线程来运行。

(4)幽灵线程

任何一个Java线程都能成为幽灵线程。它是作为运行于同一个进程内的对象和线程的服务提供者。例如,HotJava浏览器有一个称为" 后台图片阅读器"的幽灵线程,它为需要图片的对象和线程从文件系统或网络读入图片。

幽灵线程是应用中典型的独立线程。它为同一应用中的其他对象和线程提供服务。幽灵线程的run()方法一般都是无限循环,等待服务请求。

(5)线程组

每个Java线程都是某个线程组的成员。线程组提供一种机制,使得多个线程集于一个对象内,能对它们实行整体操作。譬如,你能用一个方法调用来启动或挂起组内的所有线程。Java线程组由ThreadGroup类实现。

当线程产生时,可以指定线程组或由实时系统将其放入某个缺省的线程组内。线程只能属于一个线程组,并且当线程产生后不能改变它所属的线程组。

程序

对于多线程的好处这就不多说了。但是,它同样也带来了某些新的麻烦。只要在设计程序时特别小心留意,克服这些麻烦并不算太困难。

(1)同步线程

许多线程在执行中必须考虑与其他线程之间共享数据或协调执行状态。这就需要同步机制。在Java中每个对象都有一把锁与之对应。但Java不提供单独的lock和unlock操作。它由高层的结构隐式实现, 来保证操作的对应。(然而,我们注意到Java虚拟机提供单独的monito renter和monitorexit指令来实现lock和unlock操作。)

synchronized语句计算一个对象引用,试图对该对象完成锁操作, 并且在完成锁操作前停止处理。当锁操作完成synchronized语句体得到执行。当语句体执行完毕(无论正常或异常),解锁操作自动完成。作为面向对象的语言,synchronized经常与方法连用。一种比较好的办法是,如果某个变量由一个线程赋值并由别的线程引用或赋值,那么所有对该变量的访问都必须在某个synchromized语句或synchronized方法内。

现在假设一种情况:线程1与线程2都要访问某个数据区,并且要求线程1的访问先于线程2, 则这时仅用synchronized是不能解决问题的。这在Unix或Windows NT中可用Simaphore来实现。而Java并不提供。在Java中提供的是wait()和notify()机制。使用如下:

synchronized method_1(…) { // call by thread 1.

∥access data area;

available=true;

notify();

}

synchronized method_2(…){ ∥call by thread 2.

while(!available) {

try{

wait();∥wait for notify().

} catch (Interrupted Exception

}

∥access data area

}

}

其中available是类成员变量,置初值为false。

如果在method-2中检查available为假,则调用wait()。wait()的作用是使线程2进入非运行态,并且解锁。在这种情况下,method-1可以被线程1调用。当执行notify()后。线程2由非运行态转变为可运行态。当method-1调用返回后。线程2可重新对该对象加锁,加锁成功后执行wait()返回后的指令。这种机制也能适用于其他更复杂的情况。

(2)死锁

如果程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每个线程在执行过程中都能充分访问有限的资源。系统中没有饿死和死锁的线程。Java并不提供对死锁的检测机制。对大多数的Java程序员来说防止死锁是一种较好的选择。最简单的防止死锁的方法是对竞争的资源引入序号,如果一个线程需要几个资源,那么它必须先得到小序号的资源,再申请大序号的资源。

比较

进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。

与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。

发生进程切换与发生线程切换时相比较,进程切换时涉及到有关资源指针的保存以及地址空间的变化等问题;线程切换时,由于同不进程内的线程共享资源和地址 空间,将不涉及资源信息的保存和地址变化问题,从而减少了操作系统的开销时间。而且,进程的调度与切换都是由操作系统内核完成,而线程则既可由操作系统内 核完成,也可由用户程序进行。

多线程与进程之间的关系

适用范围

典型的应用

服务器中的文件管理或通信控制前后台处理异步处理

执行特性

一个线程必须处于如下四种可能的状态:

初始:一个线程调用了new方法之后,并在调用start方法之前的所处状态。在初始态中,可以调用start和stop方法。 Runnable:一旦线程调用了start 方法,线程就转到Runnable 状态,注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。 阻塞/ NonRunnable:线程处于阻塞/NonRunnable状态,这是由两种可能性造成的:要么是因挂起而暂停的,要么是由于某些原因而阻塞的,例如包括等待IO请求的完成。退出:线程转到退出状态,这有两种可能性,要么是run方法执行结束,要么是调用了stop方法。

最后一个概念就是线程的优先级,线程可以设定优先级,高优先级的线程可以安排在低优先级线程之前完成。一个应用程序可以通过使用线程中的方法setpriority(int),来设置线程的优先级大小。

线程有5种基本操作:

派生:线程在进程内派生出来,它即可由进程派生,也可由线程派生。阻塞(Block):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。激活(unblock):如果阻塞线程的事件发生,则该线程被激活并进入就绪队列。调度(schedule):选择一个就绪线程进入执行状态。结束(Finish):如果一个线程执行结束,它的寄存器上下文以及堆栈内容等将被释放。

线程的状态与操作

线程的另一个执行特性是同步。线程中所使用的同步控制机制与进程中所使用的同步控制机制相同。

分类

线程有两个基本类型:

用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。

举例

1. UnixInternational线程

Solaris操作系统使用的线程叫做Unix International线程,支持内核线程、轻权进程和用户线程。一个进程可有大量用户线程;大量用户线程复用少量的轻权进程,轻权进程与内核线程一一对应。

用户级线程在调用核心服务时(如文件读写),需要“捆绑(bound)”在一个lwp上。永久捆绑(一个LWP固定被一个用户级线程占用,该LWP移到LWP池之外)和临时捆绑(从LWP池中临时分配一个未被占用的LWP)。

在调用系统服务时,如果所有LWP已被其他用户级线程所占用(捆绑),则该线程阻塞直到有可用的LWP。

如果LWP执行系统线程时阻塞(如read()调用),则当前捆绑在LWP上的用户级线程也阻塞。

用户线程、轻权进程和核心线程的关系

Unix International线程的有关API

UNIX International 线程的头文件是

1.创建用户级线程

int thr_create ( void * stack_base, size_t stack_size, void *(*start_routine)(void *), void * arg, long flags, thread_t * new_thr);

其中flags包括:THR_BOUND(永久捆绑), THR_NEW_LWP(创建新LWP放入LWP池),若两者同时指定则创建两个新LWP,一个永久捆绑而另一个放入LWP池。

2.等待用户级线程

int thr_join (thread_t wait_for, thread_t *dead, void **status);

3.挂起用户级线程

int thr_suspend (thread_t thr);

4.继续用户级线程

int thr_continue (thread_t thr);

5.退出用户级线程

void thr_exit (void *status);

6.返回当前用户级线程的线程标识符

thread_t thr_self ( void );

2.POSIX线程

Pthreads(POSIX Thread)的相关API。

Pthreads 线程的头文件是

1.创建用户级线程

int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void *(*start_routine) (void *), void *arg);

2.等待用户级线程

int pthread_join(pthread_t thread, void ** retval);

3.退出用户级线程

void pthread_exit(void *retval);

4.返回当前用户级线程的线程标识符

pthread_t pthread_self(void);

5.用户级线程的取消

int pthread_cancel(pthread_t thread);

3.C++11线程

C++ 11 线程的头文件是

1.创建用户级线程

std::thread::thread(Function&& f, Args&&... args);

2.等待用户级线程结束

std::thread::join();

3.脱离用户级线程控制

std::thread::detach();

4.交换用户级线程

std::thread::swap( thread& other );

4.C11线程

C11线程的头文件是

C11线程仅仅是个“建议标准”,也就是说100%遵守C11标准的C编译器是可以不支持C11线程的。根据C11标准的规定,只要编译器预定义了__STDC_NO_THREADS__宏,就可以没有头文件,自然也就也没有下列函数。

1.创建用户级线程

int thrd_create( thrd_t *thr, thrd_start_t func, void *arg );

2.结束本线程

_Noreturn void thrd_exit( int res );

3.等待用户级线程运行完毕

int thrd_join( thrd_t thr, int *res );

4.返回当前线程的线程标识符

thrd_t thrd_current();

5. Win32线程

Win32线程的上下文包括:寄存器、核心栈、线程环境块和用户栈。

Win32线程状态

就绪状态:进程已获得除处理机外的所需资源,等待执行。备用状态:特定处理器的执行对象,系统中每个处理器上只能有一个处于备用状态的线程。运行状态:完成描述表切换,线程进入运行状态,直到内核抢先、时间片用完、线程终止或进行等待状态。等待状态:线程等待对象句柄,以同步它的执行。等待结束时,根据优先级进入运行、就绪状态。转换状态:线程在准备执行而其内核堆栈处于外存时,线程进入转换状态;当其内核堆栈调回内存,线程进入就绪状态。终止状态:线程执行完就进入终止状态;如执行体有一指向线程对象的指针,可将线程对象重新初始化,并再次使用。

Windows NT的线程状态

Win32线程的有关API

Win32线程的头文件是,仅适用于Windows操作系统。

1.创建用户级线程

HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

2.结束本线程

VOID WINAPI ExitThread(DWORD dwExitCode);

3.挂起指定的线程

DWORD WINAPI SuspendThread( HANDLE hThread );

4.恢复指定线程运行

DWORD WINAPI ResumeThread(HANDLE hThread);

5.等待线程运行完毕

DWORD WINAPI WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

6.返回当前线程的线程标识符

DWORD WINAPI GetCurrentThreadId(void);

7.返回当前线程的线程句柄

HANDLE WINAPI GetCurrentThread(void);

6. Java线程

1)最简单的情况是,Thread/Runnable的run()方法运行完毕,自行终止。

2)对于更复杂的情况,比如有循环,则可以增加终止标记变量和任务终止的检查点。

3)最常见的情况,也是为了解决阻塞不能执行检查点的问题,用中断来结束线程,但中断只是请求,并不能完全保证线程被终止,需要执行线程协同处理。

4)IO阻塞和等锁情况下需要通过特殊方式进行处理。

5)使用Future类的cancel()方法调用。

6)调用线程池执行器的shutdown()和shutdownNow()方法。

7)守护线程会在非守护线程都结束时自动终止。

8)Thread的stop()方法,但已不推荐使用。

相关词条

相关搜索

其它词条