您的位置:首页 > 其它

多线程设计基本概念

2013-12-07 18:58 375 查看
进程(Processes)

进程内还有内存和资源(核心对象、user资源、GDI资源),它本身不能执行,只是一个提供安置内存和线程的地方。是一大堆对象的拥有权的集合。

内存:CODE、Data、Stack

Code是程序的可执行部分,只读的,这是CUP唯一允许执行的内存

Data是程序中的所有变量(不包括函数中的局部变量),可以分为全局变量和静态变量两种。

Stack是调用函数时用的堆栈空间,其中有局部变量,每个线程产生时配有一个堆栈。

线程启动比较快,退出比较快,彼此分享了大部分核心对象的拥有权。

要切换不同的线程,操作系统应先切换该线程所隶属之进程的内存,然后恢复该线程方在CONTEXT结构中的寄存器值,这个过程称为context switch.多CUP执行多线程可以不需要context switch.

Atomic Operations原子操纵

线程ID是一个全局变量,可独一无二的表示系统任一进程中的某个线程。

CreatThread传回来的Handle被称为一个核心对象(Kernel object),核心对象其实和GDI对象,如画笔、画刷或DC是差不多的只不过它由KERNEL32.DLL管理而非GDI32.DLL管理。HANDEL指向内存中某样东西,这样东西不允许直接访问。

核心对象包括:进程(Processes)、线程(threads)、文件(files)、事件(events)、信号量(semaphores)、互斥器(mutexes)、管道(Pipe)





GDI对象和核心对象之间有一个主要的不同,GDI对象有单一拥有者,不是进程就是线程。核心对象可以有一个以上的拥有者,甚至可以跨进程。为了保持对每一个拥有者的追踪,核心对象保持了一个引用计数,以记录有多少Handles对应的到此对象。对象中也记录了哪一个进程或线程是拥有者。

对一个大开得对象区分拥有者是进程或是线程,决定了系统何时做清除善后(cleans up)操作。Cleanup包括将该进程或线程所拥有的每一个对象的应用计数减1。程序员不能选择对象类型,一切得视对象类型而定。

如果某个进程拥有某个核心对象的Handle,而该对象的原创者(进程)已经销毁了,该核心对象并不会被摧毁。只有引用计数便为0时对象才会自动被操作系统摧毁。

“线程核心对象”应用到的那个线程也会令核心对象开启。因此对象的默认应用计数是2,当调用CloseHandle()时,应用计数下降1,当线程结束时,引用计数再降1。只有到当两件事情都发生了的时候,这个对象才会被真正清除。

可以使用ExitThread()结束线程,

主线程(Primary thread):第一、负责GUI(Graphic User Interface)程序中的主消息循环。第二、这一线程的结束会使得程序中的所有线程都被强迫结束,程序也因此而结束。

GUI线程和worker线程:GUI线程负责建造窗口以及处理主消息循环。Worker线程执行纯粹运算工作。

GUI线程定义:拥有消息队列的线程。

Worker线程不能够产生窗口、对话框、消息框,或任何其他与UI有关的东西。如果Worker线程序要输入或输出错误信息,它应该授权给UI线程来做,并把结果通知给Worker线程。

Win32提供了WaitForSingleObject()的函数使得操作系统可以让线程知道其他线程停止工作。它的第一个参数是个核心对象的handle。例如把等待的线程称为线程#1,把正在执行的线程称为线程#2,调用WaitForSingleObject()并放置一个线程核心对象最为参数,将使得线程#1开始睡眠,直到线程#2结束为止。#2为核心对象。

WaitForSingleObject()可以面对许多中handle工作,不一定是线程handle,Win32的大部分handle表示的对象都能够作为WaitForSingleObject()的等待目标。

可被WaitForSingleObject()使用的核心对象有两种状态:激发与未激发。WaitForSingleObject()会在目标物变成激发状态时返回。当核心对象被激发时,会导致WaitForSingleObject()醒来。

当线程正在执行时,线程对象处于未激发状态,当线程结束时,线程对象就被激发了。

WaitForMultipleObjects()允许在同一时间等待一个以上的对象,必须将由handles组成的数组交给次函数,并制定要等待其中的一个对象或是全部对象。

MsgWaitForMultipleObjects()函数同时等待消息或是核心对象被激发。他会在对象被激发或消息到达队列时被唤醒返回。它的最后一个参数允许指定哪些消息是观察对象。它的使用方式是改写主消息循环,使得激发状态之handles得以象消息一样的被等待,视其返回值而定,消息循环或许调用GetMessage()并处理下一个消息,或许调用定义的处理函数,以处理受激发的handles。

同步控制:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去,这就是同步”synchronous”。如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是所谓的异步”asynchronous”。Win32API中的SendMessage()就是同步行为,而PostMessage()就是异步行为。

Critical Sections(关键区域、临界区域):意指一小块用来处理一份被共享之资源的程序代码。让同一时间内只有一个线程进入critical section.

Critical section并不是核心对象。它存在于进程的内存空间,只要对它初始化。由于Critical Section不是核心对象,如果进入CriticalSection的那个线程结束了或当调了,而没有调用LeaveCriticalSection()的话,系统没有办法将该Critical Section清除,如果需要这样的机能,可以使用mutex。

当代码需要两个或更多的资源时,都有潜在性的死锁事件。

Mutex (mutual exclusion)一个时间内只能够有一个线程拥有mutex。

锁住一个未被拥有的mutex比锁住一个未被拥有的critical section需要花费几乎100倍的时间,因为critical section不需要进入系统核心。

Mutex可以跨进程使用,critical section则只能够在同一个进程中使用。

等待mutex时可以指定等待结束的时间,critical section却不能。

两个对象的相关函数

CRITICAL_SECTION

Mutex核心对象

InitializeCriticalSection

CreateMutex

EnterCriticalSection

OpenMutex

LeaveCriticalSection

WaitForSingleObject

DeleteCriticalSection

WaitForMutipleObjects

ReleaseMutex

CloseHandle

为了能够跨进程使用同一个mutex,可以在产生mutex时指定其名称。如果指定了名称,系统中的其他任何线程就可以使用这个名称来处理该mutex。一定要使用名称,因为没有办法把handle交给一个执行中的进程。

任何一个进程或线程可以根据Mutex名称打开那个mutex。

Mutex的拥有权不是属于创建它的线程,而是那个最后对此mutex进行wait…()操作的并且尚未进行releaseMutex操作的线程。Mutex在Wait…()时如果在之前没有任何的线程拥有此mutex,那么它出现短暂的激发状态,返回值后马上变为未激发状态。

如果线程在结束前没有调用ReleaseMutex把Mutex清除掉,那么Mutex不会被摧毁。该Mutex会被视为“未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0通知。

信号量(Semaphores):任何进程或线程都可根据其名称来引用到这个semaphore。

它是解决各种producer/consumer问题的关键要素。这种问题会存有一个缓冲区,可能在同一时间内被读出数据或被写入数据。

Win32中的一个Semapore可以被锁住最多N次,其中N是Semaphore被产生时指定的。N常常被设计用来代表“可以锁住一份资源”的线程个数。当每一个锁定动作成功,semaphore的现值就减1,可以使用任何一个Wait…()函数要求锁定一个semaphore,如果semaphore的现值不为0,Wait…()函数会立刻返回。

可以有一个以上的线程锁定一个semaphore,在semaphore身上没有所谓的“独占锁定”。没有拥有权的概念,也因此一个线程可以反复调用wait…()函数以产生新的锁定,直到semaphore现值下降到0,表示资源耗尽,wait..()必然进入等待。

事件(Event Objects): 用来设计某些自定义的同步对象。

Win32中最具弹性的同步机制就属events对象,Event对象是一种核心对象,它的唯一目的就是成为激发状态或未激发状态,这两种状态由程序控制,不会成为Wait…()函数的副作用。

SetEvent() 把event对象设为激发状态

ResetEvent() 把event对象设为非激发状态

PulseEvent() 如果是一个Manual Reset Event:把event对象设为激发状态,唤醒“所有”等待中的线程,然后event恢复为非激发状态。如果是一个Auto Reset Event: 把event对象设为激发状态,唤醒“一个”等待中的县城,然后event恢复为非激发状态。

如果选择一个新的event类型(Automatic或Manual)原油的event对象和线程统统会被摧毁,程序重新产生出新的event和新的线程。

Interlocked Variables :interlockedIncrement();InterlockedDecrement();

这两个函数都只能和0做比较,不能和任何其他数值比较。

同步机制的最简单类型是使用interlocked函数,对着标准的32位变量进行操作。这些函数没有提供“等待”机能,他们只是保证对某个特定变量的存取操作是“一个一个接顺序来”。

InterlockefDecrement()先将计数器内容减1,再将其值与0做比较,并且传回比较结果。返回值表示与0作比较的结果。

TerminateThread()强制运行中的线程结束,使得线程没有任何机会在结束前清理自己。容易造成大量的内存泄露。所以尽量远离terminateTread()。

使用信号(Signals)

线程优先权(tread Priority):

Win32优先权是以数值表现的,并以进程的“优先权类别(Priority class)”、线程的”优先权层级(Priority level)”和操作系统当时采用的”动态提升(Dynamic Boost)”作为计算基准。所有因所放在一起,最后获得一个0-31的数值,拥有最高优先权的线程,即为下一个执行起来的线程。

优先权类别(priority Class):是进程的属性之一。这个属性可以表现出这一进程和其他进程比较之下的重要性。

优先权类别(Priority Classes)

优先权类别(Priority Classes)
基础优先权限(base priority)
HIGH_PRIORITY_CLASS
13
IDLE_PRIORITY_CLASS
4
NORMAL_PRIORITY_CLASS
7or8
REALTIME_PRIORITY
24
优先权类别适用于进程而非线程。可以用SetPriorityClass()和GetPriorityClass()来调整和验证其值。

优先权层级(Priority Level):是对进程的优先权类别的一个修改。能够调整同一进程内各线程的相对重要性。一共有七种优先层级。

优先权层级(Priority Levels)

优先权层级(Priority Levels)

调整值

THREAD_PRIORITY_HIGHEST

+2

THREAD_PRIORITY_ABOVE_NORMAL

+1

THREAD_PRIORITY_NORMAL

0

THREAD_PRIORITY_BELOW_NORMAL

-1

THREAD_PRIORITY_LOWEST

-2

THREAD_PRIORITY_IDLE

Set to 1

THREAD_PRIORITY_TIME_CRMCAL

Set to 15

优先权层级可以用SetThreadPriority()改变之

线程目前的优先权层级可以利用getThreadPriority()获知。

动态提升(Dynamic Boost): 决定线程真正优先权的最后一个因素。

所谓动态提升是对优先权的一种调整,使系统能够机动对待线程,以强化程序的可用性。

挂起(suspending)一个线程:SuspendThread()函数,允许调用端指定一个线程睡眠(挂起),直到又有人调用ResumeThread(),线程才会醒来。因此睡眠中的线程不可能唤醒自己。

Overlapped I/O(asynchronous I/O):可以利用此技术要求操作系统传送数据,并且在传送完毕时通知,这项技术使得程序在I/O进行过程中仍然能够继续处理事务。

文件操作函数:Win32之中的三个基本执行I/O的函数:

CreateFile()

ReadFile()

WriteFile()

只要用CloseHandle()关闭文件。

CreateFile()可以打开各式各样的资源,包括:文件(硬盘,软盘,光盘或其他)、串行口和并行口(serial and parallel ports)、named pipes、Console

Overlapped I/O性质:可以在同一时间读(或写)文件的许多部分。这些操作都使用相同的文件handle。

Overlapped I/O的基本形式是以ReadFile()和WriteFile()完成的。

OVERLAPPED结构:执行两个重要的功能,第一、它像一把钥匙,用以识别每一个目前正在进行的overlapped操作。第二、它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。

如果需要等待Overlapped I/O的执行结果,通过核心对象文件的handle,当完成操作之后,调用GetOverlappedResult()以确定结果如何。

使用event对象搭配overlapped I/O,可以对同一个文件发出多个读取操作和多个写入操作,每一个操作有自己的event对象;然后再调用waitForMultipleObjects()来等待其中之一(或全部)完成。最能能够等待MAXNUM_WAIT_OBJECTS个对象,此最大值为64。

异步过程调用(Asynchronous Procedure Call,APC):只要使用”EX”版的readfile()和writeFile(),就可以调用这个机制。这两个函数允许指定一个额外的参数:Callback函数地址。此函数会在线程进入alterable状态时被调用。

如果线程因为以下五个函数而处于等待状态,而其“alertable”标记被设为true,则该线程就是处于”alertable”状态:

SleepEx()

WaitForSingleObjectEx()

WaitForMultipleObjectEx()

MsgWaitForMultipleObjectsEx()

SignalObjectAndWait()

I/O completion port是一种特殊的核心对象,用来综合一堆线程,让它们为”overlapped请求”服务,其所提供的功能甚至可以跨越多个cpus。Completion port可以自动补偿成长中的服务器,适合应用于沉重的负担。

Completion port:他是一个机制,用来管理一堆线程如何为completed overlapped I/O requests服务。

操作概观:

1、 产生一个I/O completion port

2、 让它和一个文件handle产生联系

3、 产生一堆线程

4、 让每一个线程都在completion port上等待

5、 开始对着那个文件handle 发出一些overlapped I/O请求

当文件被开启时,它们可以在任何时候与I/O completion port产生关联,在completion port上等待的线程不应该做“为completion port服务”以外的事情,因为这些线程将一直都是completion port所持续追踪的那一堆线程的一部分。

产生一个I/O Completion Port:I/O Completion Port是一个核心对象,使用CreateCompletionPort()才能产生它。

任何文件只要附着到一个I/O Completion port身上,都必须以FILE_FLAG_OVERLAPPED开启。如果已经附着上去,就不能够再以ReadFileEx()或WriteFileEx()操作它。

在Completion Port上等待的线程是以先进后出(first in last out,FILO)的次序提供服务。

如下的调用可以启动“能够被一个I/O completion port掌握”的I/O操作:

ConnectNamePipe()

DeviceIoControl()

LockFileEx()

ReadFile()

TransactNamePipe()

WaitCommEvent()

WriteFile()

为了使用CompletionPort,主线程(或任何其他线程)可以对着一个与此completion port有关联的文件,进行读,写,或其他任何操作。该线程不需要调用WaitForMultipleObjects(),因为池子里的各个线程都曾经调用过GetQueuedCompletionStatus(),一旦I/O操作完成,一个等待中的线程将会自动被释放,以服务该操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: