多线程一
2013-10-21 13:13
253 查看
一些基础知识
进程包含内存和资源。资源包括内核对象,用户资源,以及GDI资源。
内存主要包括:代码,数据(全局变量和静态变量,以及动态分配的控件),以及堆栈。
在同一个进程中的线程大家共用内存以及资源。
context switch(CONTEXT结构):用于线程切换场景保存。
race conditions(竞争条件)
多线程无法预期,执行持续无法保证。
1.内核对象
内核对象可以拥有多个拥有者,甚至可以是跨进程的。内核对象使用引用计数来表示有多少拥有者,内核对象中也记录了那个线程或进程是拥有者。当我们调用返回handle的函数时,引用计数会加1,调用CloseHandle函数,引用计数会减1.当引用计数为0时,内核对象将被操作系统从内存中销毁。
常见的内核对象:进程,线程,文件,事件,信号量,互斥器,管道。
释放内核对象:CloseHandle(HANDLE hHandle);//引用计数减1.
如果进程结束前没有对它所调用的内核对象调用CloseHandle函数,系统会自动对这些内核对象引用计数减1.但,建议一定要调用CloseHandle函数,因为可能造成资源泄漏。
线程内核对象:当线程正在执行时,处于未激发状态;当线程结束运行时,处于激发状态。当线程内核对象处于激发对象时,其他等待的线程,都会被唤醒。
进程内核对象:当进程正在执行时,处于未激发状态;当进程结束运行时,出于激发状态。
文件内核对象:当console窗口的输入缓冲区有数据可用时,将处于激发状态。
事件内核对象:通过CreateEvent函数来设置开始时的状态。其可以分为人工重置和自动重置。人工重置,可以通过SetEvent函数和ResetEvent函数来设置状态。自动重置,当等待函数返回后,自动将事件内核对象设置为激发状态。
信号量内核对象:当计数器的值大于0时,处于激发状态;当计数器等于0时,处于未激发状态。
互斥内核对象:如果一个互斥量没被任何线程拥有,它将处于激发状态;一旦一个等待互斥量的函数返回时,它将自动被设置为未激发状态。
2.线程内核对象
线程内核对象引用到的那个线程也会另这个内核对象开启,因此线程内核对象的默认计数为2,当调用CloseHandle时,引用计数减1,当线程结束时,引用计数再减1.
win32函数错误处理:一个宏:MTVERITY。如果win32函数执行失败,其会打印一行文字说明.
3.线程函数
(1).创建线程的函数:CreateThread(...),其调用成功会返回一个线程的handle,调用失败会返回FALSE。其还会传回一个线程ID。
线程函数的原型为:DROD WINAPI 函数名(LVOID 参数名);
handle是一个内核对象,实际上是一个指针,其指向内存的某样东西,你不能够直接取得这样东西。handle为进程所有。
线程ID可以表示系统中任意进程中的线程,即时唯一的。
(2).将线程挂起调用函数:SuspendThread()(可以将其他线程挂起);
(3).将挂起的线程恢复执行调用函数:ResumeThread();
(4).结束线程:VOID ExitThread(DWORD dwExitCode);
(5).获取线程结束代码:GetExitCodeThread(HANDLE,LPDWORD);
//执行成功返回TRUE,执行失败返回FLASE,可以通过函数GetLastError获取失败原因。如果线程仍在执行,则第二个参数为STILL_ACTIVE。
等待函数:
(1):WaitForSingleObject();等待目标内核对象变为激发状态,返回WAIT_OBJECT_0;时间结束,返回WAIT_TIMEOUT;
(2):WaitForMultipleObject():可以等待多个目标内核对象,有两种情况的设置:一种为等待对象中有一个对象处于激发状态就返回,一种为全部对象处于激发状态才返回。其返回值有:WAIT_TIMEOUT;WAIT_OBJECT_0;返回值-WAIT_OBJECT_0;WAIT_FAILED;当等待对象有互斥量时,返回值可能为:WAIT_ABANDONED_0~WAIT_ABANDONED_0_nCount-1;
(3):MsgWaitForMultipleObject():它会在对象被激发或消息到达队列时被唤醒时返回。其相比WaitForMultipleObject函数多一个返回值WAIT_OBJECT_0+nCount,表示消息到达消息队列。
线程同步
1.临界区
临界区不是内核对象,其只在存在同一个进程中,也就是说只能同一个进程的线程才能使用它。当一个线程通过EnterCriticalSection函数进入临界区后,可以多次重复进入该临界区,离开时也应该调用相同次数的Leave函数。
使用方法:1)声明一个类型为CRITICAL_SECTION的变量;
2)使用InitializeCriticalSection()函数对该变量进行初始化。
3)线程使用EnterCriticalSection()函数进入临界区,这样该临界区被锁定,其它线程就不能访问了。
4)线程是用LeaveCritcalSection()函数离开临界区,其他线程获得禁区临界区的机会。
5)使用 DeleteCriticalSection()函数对该变量进行清除。
注:如果进入临界区的线程结束了或死掉了,但没有调用LeaveCriticalSection函数,系统是没有办法将该临界区清除的,因为临界区不是内核对象。
2. 互斥器
互斥器是内核对象,其能够被不同进程的线程拥有,其相对于临界区花费的时间更多(大约100倍)。
当没有线程拥有互斥器时,处于激活状态(应该说:当线程没有被任何线程拥有时,该互斥器处于未激活状态。当有一个Wait函数处于等待状态时,互斥器会短暂处于激活状态,这样等待函数得以返回,而互斥器就变为未激活和状态)。当有线程拥有互斥器时,处于未激活状态。
1)创建一个互斥器:CreateMutex(). 如果创建的互斥器已经存在(名称存在),则会返回一个已经存在的互斥器的句柄。
通过名字打开一个互斥器:OpenMutex().
2)释放互斥器:ReleaseMutex(); 当你用等待函数获取拥有权后,都要调用该函数,已释放拥有权。
3)CloseHandle();
如果拥有该互斥量的线程在结束时(不论是调用ExitThread还是死掉),没有释放掉这个互斥量(即调用ReleaseMutex),那么这个互斥量会被视为“未拥有”及“未激活状态”。而这时,如果有WaiForSingleObject函数处于等待状态会返回WAIT_ABANDONED_0,如果是WaitForMultipleObject函数则返回WAIT_ABANDONED_0到WAIT_ABANDON_0+n-1中的一个值,表示那个互斥器处于该状态。
3.信号量
大于0.处于激发状态,等于0处于未激发状态。
和互斥量的区别:(1)对于信号量,一个线程可以反复调用Wait函数参数新的锁定,因为信号量不会有“拥有权”问题。而对于互斥器来说,一个拥有互斥器的线程不论调用多少次Wait函数,都不会处于阻塞态。(2)和互斥器不同,调用ReleaseSemaphore函数的线程不一定是调用等待函数的那个线程。任何线程在任何时间都可以调用ReleaseSemaphore函数,来释放任何线程锁定的信号量。
1)创建信号量:CreateSemaphore()
运用名称打开一个已经存在的信号量:OpenSemaphore()
2)解除锁定:ReleaseSemaphore() 当你用等待函数后,都要调用。将其值加一,加一后的值不会大于最大值。
3)CloseHandle()
4.事件
事件和互斥器、信号量一样都是内核对象。事件的唯一目的是成为激发状态或未激发状态,状态由程序控制,不受等待函数的影响(副作用)。
1)创建事件:CreateEvent() 可以通过该函数来设置事件的起始状态,以及状态是人工重置还是自动重置。
打开已有事件对象:OpenEvent()
2)CloseHandle();
SetEvent():将事件设置为激发状态,
人工重置:调用ResetEvent函数将事件设置为未激发状态。
自动重置:当事件处于激发态(唤醒一个线程后)后,事件自动重置为未激发状态。
PluseEvent:当是人工重置时,将事件设为激发状态,唤醒所有等待的线程,然后事件恢复未激发状态;当是自动重置时,将事件设为激发状态,唤醒一个等待的线程,然将事件设为未激发状态。
5.Interlocked函数
Interlocked函数对32的变量进行操作,无“等待”机能,其有两个函数(都只能和0比较):
1)InterlockedIncrement():递增
2)InterlockedDecrement():递减
3)InterlockedExchang():锁定一个新值,返回旧值
结束线程
1.使用TerminateThread()函数强制结束函数。
造成的影响:线程没来得及清理自己;线程的堆栈没释放;只是将线程变为激活状态。因此尽量不要用这个函数。
2.调用函数:ExitThread();
3.线程函数正常结束。
优先级
决定线程优先级的三个因素分别为:优先级类别、优先级层次和动态提升值。
1.优先权类别
4个优先权类别:
HIGH_PRIORITY_CLASS 基础优先权值为13;IDLE_PRIORITY_CLASS 基础优先权值为4;
NORMAL_PRIORITY_CLASS 基础优先权值为7或者8;REALTIME_PRIOTITY_CLASS 基础优先权为24;
优先权类别使用于进程不适用于线程。可以通过SetPriorityClass函数和GetPriorityClass函数来调整和获取优先级类别值。
2.优先级层次
优先级层次是对进程的优先权类别的修改,其用于调整进程中线程的相对重要性。可以通过SetThreadPriority函数和GetThreadPriority函数来改变和获取优先级层次的值。
7个优先级层次为:
THREAD_PRIORITY+HIGHEST 调整值为:+2
THREAD_PRIORITY_ABOVE_NORMAL 调整值为:+1
THREAD_PRIROTY_NORMAL 调整值为:0
THREAD_PRIROTY_BELOW_NORMAL 调整值为:-1
THREAD_PRIROTY_LOWEST 调整值为:-2
THREAD_PRIROTY_IDLE 调整值为:Set to 1
THREAD_PRIROTY_TIME_CRITICAL 调整值为:Set to 15
3.动态提升
动态提升是指:对优先权的一种调整,是系统能够机动对待线程,已强化程序的可用性。
在下列5种情况下,Windows 会提升线程的当前优先级:
A) I/O操作完成
B) 信号量或事件等待结束
C) 前台进程中的线程完成一个等待操作
D) 由于窗口活动而唤醒图形用户接口线程
E) 线程处于就绪状态超过一定时间,但没能进入运行状态(处理机饥饿)
重叠I/O(overlapped I/O)
win32的文件操作函数
A) CreateFile():用于打开资源各种资源,包括(不限于)文件、串行口和并行口、命名管道、Console。
B) ReadFile()和WriteFile():可以同时读文件的不同部分,没有目前文件位置的概念。
OVERLAPPED结构体:
typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;//文件开始读火邪相对文件头开始的便宜位置
DWORD OffsetHigh;
HANDLE hEvent;一个手动充值的事件内核对象,在重叠I/0完成即被激发、
}OVERLAPPED,*LPOVERLAPPED;
被激发的事件内核对象
使用重叠I/O并搭配事件内核对象,使用WaitForMultipleObject函数,最多才能等待MAXIMUM_WAIT_OBJECTS个对象(一般操作系统定义为64个),再多就无法处理了。在一个就是你需要不断的判断是那个handle被激发。
异步过程调用
为了解决上面使用内核对象带来的两个问题,我们可以使用带“EX”的两个ReadFile和WriteFile函数。这两函数多了一个参数,用于指定回调函数。这个函数只有在重叠I/O完成后,且系统处于“alertable”状态时,才会调用。当系统不处于“alertable”状态时,回到函数会等待其处于该状态后在运行。以下5个函数处于等待状态,会将“alertable”标记设为TRUE,因而处于“alertable”状态:
A) SleepEx()
B) WaitForsingleobjectEx()
C) WaitForMultipleObjectEx()
D) MsgWaitForMultipleObjectEx()
E) SignalObjectAndWait()
回调函数的原型:
VOID WINAPI FileIOCompletionRoution(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
);
进程包含内存和资源。资源包括内核对象,用户资源,以及GDI资源。
内存主要包括:代码,数据(全局变量和静态变量,以及动态分配的控件),以及堆栈。
在同一个进程中的线程大家共用内存以及资源。
context switch(CONTEXT结构):用于线程切换场景保存。
race conditions(竞争条件)
多线程无法预期,执行持续无法保证。
1.内核对象
内核对象可以拥有多个拥有者,甚至可以是跨进程的。内核对象使用引用计数来表示有多少拥有者,内核对象中也记录了那个线程或进程是拥有者。当我们调用返回handle的函数时,引用计数会加1,调用CloseHandle函数,引用计数会减1.当引用计数为0时,内核对象将被操作系统从内存中销毁。
常见的内核对象:进程,线程,文件,事件,信号量,互斥器,管道。
释放内核对象:CloseHandle(HANDLE hHandle);//引用计数减1.
如果进程结束前没有对它所调用的内核对象调用CloseHandle函数,系统会自动对这些内核对象引用计数减1.但,建议一定要调用CloseHandle函数,因为可能造成资源泄漏。
线程内核对象:当线程正在执行时,处于未激发状态;当线程结束运行时,处于激发状态。当线程内核对象处于激发对象时,其他等待的线程,都会被唤醒。
进程内核对象:当进程正在执行时,处于未激发状态;当进程结束运行时,出于激发状态。
文件内核对象:当console窗口的输入缓冲区有数据可用时,将处于激发状态。
事件内核对象:通过CreateEvent函数来设置开始时的状态。其可以分为人工重置和自动重置。人工重置,可以通过SetEvent函数和ResetEvent函数来设置状态。自动重置,当等待函数返回后,自动将事件内核对象设置为激发状态。
信号量内核对象:当计数器的值大于0时,处于激发状态;当计数器等于0时,处于未激发状态。
互斥内核对象:如果一个互斥量没被任何线程拥有,它将处于激发状态;一旦一个等待互斥量的函数返回时,它将自动被设置为未激发状态。
2.线程内核对象
线程内核对象引用到的那个线程也会另这个内核对象开启,因此线程内核对象的默认计数为2,当调用CloseHandle时,引用计数减1,当线程结束时,引用计数再减1.
win32函数错误处理:一个宏:MTVERITY。如果win32函数执行失败,其会打印一行文字说明.
3.线程函数
(1).创建线程的函数:CreateThread(...),其调用成功会返回一个线程的handle,调用失败会返回FALSE。其还会传回一个线程ID。
线程函数的原型为:DROD WINAPI 函数名(LVOID 参数名);
handle是一个内核对象,实际上是一个指针,其指向内存的某样东西,你不能够直接取得这样东西。handle为进程所有。
线程ID可以表示系统中任意进程中的线程,即时唯一的。
(2).将线程挂起调用函数:SuspendThread()(可以将其他线程挂起);
(3).将挂起的线程恢复执行调用函数:ResumeThread();
(4).结束线程:VOID ExitThread(DWORD dwExitCode);
(5).获取线程结束代码:GetExitCodeThread(HANDLE,LPDWORD);
//执行成功返回TRUE,执行失败返回FLASE,可以通过函数GetLastError获取失败原因。如果线程仍在执行,则第二个参数为STILL_ACTIVE。
等待函数:
(1):WaitForSingleObject();等待目标内核对象变为激发状态,返回WAIT_OBJECT_0;时间结束,返回WAIT_TIMEOUT;
(2):WaitForMultipleObject():可以等待多个目标内核对象,有两种情况的设置:一种为等待对象中有一个对象处于激发状态就返回,一种为全部对象处于激发状态才返回。其返回值有:WAIT_TIMEOUT;WAIT_OBJECT_0;返回值-WAIT_OBJECT_0;WAIT_FAILED;当等待对象有互斥量时,返回值可能为:WAIT_ABANDONED_0~WAIT_ABANDONED_0_nCount-1;
(3):MsgWaitForMultipleObject():它会在对象被激发或消息到达队列时被唤醒时返回。其相比WaitForMultipleObject函数多一个返回值WAIT_OBJECT_0+nCount,表示消息到达消息队列。
线程同步
1.临界区
临界区不是内核对象,其只在存在同一个进程中,也就是说只能同一个进程的线程才能使用它。当一个线程通过EnterCriticalSection函数进入临界区后,可以多次重复进入该临界区,离开时也应该调用相同次数的Leave函数。
使用方法:1)声明一个类型为CRITICAL_SECTION的变量;
2)使用InitializeCriticalSection()函数对该变量进行初始化。
3)线程使用EnterCriticalSection()函数进入临界区,这样该临界区被锁定,其它线程就不能访问了。
4)线程是用LeaveCritcalSection()函数离开临界区,其他线程获得禁区临界区的机会。
5)使用 DeleteCriticalSection()函数对该变量进行清除。
注:如果进入临界区的线程结束了或死掉了,但没有调用LeaveCriticalSection函数,系统是没有办法将该临界区清除的,因为临界区不是内核对象。
2. 互斥器
互斥器是内核对象,其能够被不同进程的线程拥有,其相对于临界区花费的时间更多(大约100倍)。
当没有线程拥有互斥器时,处于激活状态(应该说:当线程没有被任何线程拥有时,该互斥器处于未激活状态。当有一个Wait函数处于等待状态时,互斥器会短暂处于激活状态,这样等待函数得以返回,而互斥器就变为未激活和状态)。当有线程拥有互斥器时,处于未激活状态。
1)创建一个互斥器:CreateMutex(). 如果创建的互斥器已经存在(名称存在),则会返回一个已经存在的互斥器的句柄。
通过名字打开一个互斥器:OpenMutex().
2)释放互斥器:ReleaseMutex(); 当你用等待函数获取拥有权后,都要调用该函数,已释放拥有权。
3)CloseHandle();
如果拥有该互斥量的线程在结束时(不论是调用ExitThread还是死掉),没有释放掉这个互斥量(即调用ReleaseMutex),那么这个互斥量会被视为“未拥有”及“未激活状态”。而这时,如果有WaiForSingleObject函数处于等待状态会返回WAIT_ABANDONED_0,如果是WaitForMultipleObject函数则返回WAIT_ABANDONED_0到WAIT_ABANDON_0+n-1中的一个值,表示那个互斥器处于该状态。
3.信号量
大于0.处于激发状态,等于0处于未激发状态。
和互斥量的区别:(1)对于信号量,一个线程可以反复调用Wait函数参数新的锁定,因为信号量不会有“拥有权”问题。而对于互斥器来说,一个拥有互斥器的线程不论调用多少次Wait函数,都不会处于阻塞态。(2)和互斥器不同,调用ReleaseSemaphore函数的线程不一定是调用等待函数的那个线程。任何线程在任何时间都可以调用ReleaseSemaphore函数,来释放任何线程锁定的信号量。
1)创建信号量:CreateSemaphore()
运用名称打开一个已经存在的信号量:OpenSemaphore()
2)解除锁定:ReleaseSemaphore() 当你用等待函数后,都要调用。将其值加一,加一后的值不会大于最大值。
3)CloseHandle()
4.事件
事件和互斥器、信号量一样都是内核对象。事件的唯一目的是成为激发状态或未激发状态,状态由程序控制,不受等待函数的影响(副作用)。
1)创建事件:CreateEvent() 可以通过该函数来设置事件的起始状态,以及状态是人工重置还是自动重置。
打开已有事件对象:OpenEvent()
2)CloseHandle();
SetEvent():将事件设置为激发状态,
人工重置:调用ResetEvent函数将事件设置为未激发状态。
自动重置:当事件处于激发态(唤醒一个线程后)后,事件自动重置为未激发状态。
PluseEvent:当是人工重置时,将事件设为激发状态,唤醒所有等待的线程,然后事件恢复未激发状态;当是自动重置时,将事件设为激发状态,唤醒一个等待的线程,然将事件设为未激发状态。
5.Interlocked函数
Interlocked函数对32的变量进行操作,无“等待”机能,其有两个函数(都只能和0比较):
1)InterlockedIncrement():递增
2)InterlockedDecrement():递减
3)InterlockedExchang():锁定一个新值,返回旧值
结束线程
1.使用TerminateThread()函数强制结束函数。
造成的影响:线程没来得及清理自己;线程的堆栈没释放;只是将线程变为激活状态。因此尽量不要用这个函数。
2.调用函数:ExitThread();
3.线程函数正常结束。
优先级
决定线程优先级的三个因素分别为:优先级类别、优先级层次和动态提升值。
1.优先权类别
4个优先权类别:
HIGH_PRIORITY_CLASS 基础优先权值为13;IDLE_PRIORITY_CLASS 基础优先权值为4;
NORMAL_PRIORITY_CLASS 基础优先权值为7或者8;REALTIME_PRIOTITY_CLASS 基础优先权为24;
优先权类别使用于进程不适用于线程。可以通过SetPriorityClass函数和GetPriorityClass函数来调整和获取优先级类别值。
2.优先级层次
优先级层次是对进程的优先权类别的修改,其用于调整进程中线程的相对重要性。可以通过SetThreadPriority函数和GetThreadPriority函数来改变和获取优先级层次的值。
7个优先级层次为:
THREAD_PRIORITY+HIGHEST 调整值为:+2
THREAD_PRIORITY_ABOVE_NORMAL 调整值为:+1
THREAD_PRIROTY_NORMAL 调整值为:0
THREAD_PRIROTY_BELOW_NORMAL 调整值为:-1
THREAD_PRIROTY_LOWEST 调整值为:-2
THREAD_PRIROTY_IDLE 调整值为:Set to 1
THREAD_PRIROTY_TIME_CRITICAL 调整值为:Set to 15
3.动态提升
动态提升是指:对优先权的一种调整,是系统能够机动对待线程,已强化程序的可用性。
在下列5种情况下,Windows 会提升线程的当前优先级:
A) I/O操作完成
B) 信号量或事件等待结束
C) 前台进程中的线程完成一个等待操作
D) 由于窗口活动而唤醒图形用户接口线程
E) 线程处于就绪状态超过一定时间,但没能进入运行状态(处理机饥饿)
重叠I/O(overlapped I/O)
win32的文件操作函数
A) CreateFile():用于打开资源各种资源,包括(不限于)文件、串行口和并行口、命名管道、Console。
B) ReadFile()和WriteFile():可以同时读文件的不同部分,没有目前文件位置的概念。
OVERLAPPED结构体:
typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;//文件开始读火邪相对文件头开始的便宜位置
DWORD OffsetHigh;
HANDLE hEvent;一个手动充值的事件内核对象,在重叠I/0完成即被激发、
}OVERLAPPED,*LPOVERLAPPED;
被激发的事件内核对象
使用重叠I/O并搭配事件内核对象,使用WaitForMultipleObject函数,最多才能等待MAXIMUM_WAIT_OBJECTS个对象(一般操作系统定义为64个),再多就无法处理了。在一个就是你需要不断的判断是那个handle被激发。
异步过程调用
为了解决上面使用内核对象带来的两个问题,我们可以使用带“EX”的两个ReadFile和WriteFile函数。这两函数多了一个参数,用于指定回调函数。这个函数只有在重叠I/O完成后,且系统处于“alertable”状态时,才会调用。当系统不处于“alertable”状态时,回到函数会等待其处于该状态后在运行。以下5个函数处于等待状态,会将“alertable”标记设为TRUE,因而处于“alertable”状态:
A) SleepEx()
B) WaitForsingleobjectEx()
C) WaitForMultipleObjectEx()
D) MsgWaitForMultipleObjectEx()
E) SignalObjectAndWait()
回调函数的原型:
VOID WINAPI FileIOCompletionRoution(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
);
相关文章推荐
- windows游戏程序自动备份与更新
- 奥卡姆剃刀定律在程序设计中的应用[化繁为简]
- RADIUS
- hdu 1676(二维Dijkstra)
- 代码重构方向原则指导(转载 cnblogs)
- delphi 换行操作 Word
- 第十周项目6-吃饭,睡觉,打豆豆(不使用自定义函数)(使用if-else语句)
- 站得高看得远——早会激励小故事
- 编程之美_2.10_寻找数组中的最大值和最小值
- Linux无光盘安装yum
- eCos中的main函数 .
- Linux与VxWorks主要区别(二):运行模式
- Javascript实现真实字符串剩余字数提示
- String的疑问
- 程序员该如何去写自己的简历-代码而成的简历
- yum故障最终解决办法.移魂版 bash:yum:command not found
- H264编码 封装成MP4格式 视频流 RTP封包
- MongoDB的安装与配置
- jsp笔记
- 重载,覆盖,多态