当前位置:文档之家› 多线程及其同步

多线程及其同步



(2)/MT和/MTd表示采用多线程运行时库的静态lib版本。该选项会在 编译时将运行时库以静态lib的形式完全嵌入。该选项生成的可执行文
件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终
生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。

(3)/MD和/MDd表示采用多线程运行时库的动态dll版本,该方式不会

要说明的一点是,对于单核处理器(CPU) 的计算机,操作系统给每个线程分配不同的 CPU时间片,在某一个时刻,CPU只执行一 个时间片内的线程,多个时间片中的相应线 程在CPU内轮流执行,由于每个时间片时间 很短,所以对用户来说,仿佛各个线程在计 算机中是并行处理的。操作系统根据线程的 优先级来安排CPU的时间,优先级高的线程 优先运行,优先级低的线程则继续等待。
将运行时库链接到可执行文件内部,只在运行时才调用,所以可有效
减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一 个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。
线程函数的定义

DWORD WINAPI ThreadProc(LPVOID lpParam); WINAPI是一个宏名,在 windef.h文件中有如下的声明: #define WINAPI __stdcall __stdcall 是Windows标准 C/C++函数的调用方法。从底层上说, 使用这种调用方法,参数的进栈顺序和标准 C调用(__cdecl 方 法)是一样的,都是从右到左,但是__stdcall 采用自动清栈的 方式,而__cdecl 采用的是手工清栈方式,并且函数名自动加前 导的下划线。 Windows 规定,凡是由它来负责调用的函数都必须定义为 __stdcall 类型。 ThreadProc是一个回调函数,即由Windows系统来负责调用的函 数,所以此函数应定义为__stdcall类型。另外,如果没有显式说 明的话,函数的调用方法默认是__cdecl。
线程上下文


线程的上下文本质上是一组处理器的寄存器,上下文 及其转换的过程根据处理器的结构不同会有所不同。 大约每经 20ms,Windows 查看一次当前存在的所有 线程内核对象。在这些对象中,只有少部分是可调度 的(没有处于暂停状态),Windows 选择其中的一个 内核对象,将它的CONTEXT(上下文)装入 CPU的 寄存器,这一过程称为上下文切换。 用户可调用GetThreadContext查看当前线程的用户模 式的上下文信息;调用SetThreadContext改变线程上 下文,待下次调度进CPU时生效。其中,ContextFlags 参数,通过异或掩码指定欲查看的寄存器。 KTHREAD::ContextSwitches为线程已切换的次数。
Release模式。
多线程版本的重大改变是,第一,一些全局变量如errno ,现在变成每个执行线 程各拥有一个。第二,多线程版中的数据结构以同步机制加以保护。
Visual C++共提供的6个运行时库
Reusable Library (可用函数库版本) Switch (编译选项) Library (库) Macro(s) Defined (宏定义)
参数说明:




参数1:为线程的安全属性,一般设为NULL,表示使用默认安全属性。 参数2:为线程堆栈大小,一般设为NULL,表示使用默认堆栈大小, 对应VC的/STACK:链接器选项。VC6默认的堆栈大小为1M,可通过 “Project Settingsà Linkà Stack allocations”设臵堆栈大小;VC2005中,可 在“项目属性à 配臵属性à 链接器à 系统”中设臵堆栈大小。 参数3:为线程函数的地址,传递函数指针或函数名ThreadProc。 参数4:为传递给线程函数的参数,即ThreadProc的参数。其为 LPVOID类型,对复杂的参数采用结构体或类按址传递。 参数5:为线程创建参数,例如线程创建后是否立即启动的开关选项。 参数6:为内核给新创建的线程分配的线程ID号,为输出参数。 此函数执行成功后,将返回新建线程的线程句柄。lpStartAddress参数 指定了线程函数的地址,新建线程将从此地址开始执行,直到 return 语句返回,线程运行结束,把控制权交给操作系统。

API对线程的支持




Windows创建新线程的API函数是CreateThread,由该函数 创建的线程,将在调用者的虚拟地址空间内执行。函数原 型如下: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier );
Single Threaded(static) MultiThreaded(static) Dynamic Link (DLL) (Multithreaded) Debug SingleThreaded(static) Debug Static MultiThreaded Debug Dynamic Link (DLL) (Multithreaded)
多线程的提出


在没有出现多核CUP之前,我们的计算资源是唯一的,也 就是说,在任一时刻最多只有一个进程可以使用处理机。 如果编写一个耗时的单线程程序:比如,新建一个基于对 话框的应用程序SingleThread,在主对话框添加一个按钮, 标题为“延时6秒”,添加按钮的响应函数,代码如下: void CSingleThreadDlg::OnSleepSixSecond() { Sleep(6000); //延时6秒 } 编译并运行应用程序,单击“延时6秒”按钮,你就会发 现在这6秒期间程序就象“死机”一样,不在响应其它消息。 为了更好地处理这种耗时的操作,便提出了多线程的概念。
使用计数


Usage Count成员记录了线程内核对象的使用计数,这个计数说 明了此内核对象被打开的次数。线程内核对象的存在与 Usage Count 的值息息相关,当这个值是0 的时候,系统就认为已经没 有任何进程在引用此内核对象了,于是线程内核对象就要从内 存中撤销。 只要线程没有结束运行,Usage Count 的值就至少为 1。在创建 一个新的线程时,CreateThread 函数返回了线程内核对象的句 柄,相当于打开一次新创建的内核对象,这也会促使 Usage Count 的值加1。所以创建一个新的线程后,初始状态下 Usage Count 的值是2。之后,只要有进程打开此内核对象,就会使 Usage Count的值加1。比如当有一个进程调用OpenThread函数 打开这个线程内核对象后,Usage Count 的值会再次加 1。由于 对这个函数的调用会使 Usage Count 的值加1,所以在使用完它 们返回的句柄后一定要调用 CloseHandle 函数进行关闭。关闭内 核对象句柄的操作就会使 Usage Count 的值减 1。
/ML /MT
libc.lib libcmt.lib
(none) _MT
/MD
msvcrt.lib
_MT and _DLL
/MLd /MTd
libcd.lib libcmtd.lib
_DEBUG _DEBUG and _MT
/MDd
msvcrtd.lib
_DEBUG, _MT, _DLL
说明

(1)从Visual C++ 2005开始,libcp.lib和libcpd.lib(老的/ML和/MLd选项) 已经被移除。通过/MT和/MTd使用libcpmt.lib和libcpmtd.lib取代。
多线程

用户根据需要在应用程序中创建其它线程,在单个程 序中同时(并发地)运行多个线程完成不同的工作, 称为多线程。
线程2
进程

线
程 线程3

一个进程中的所有线程都在该进程的虚拟地址空间中, 共同使用这些虚拟地址空间、全局变量和系统资源, 多线程可以实现并行处理,避免了某项任务长时间占 用CPU时间。
目录
一、线程相关概念(基础理论) 1、程序与进程 2、线程与主线程 3、多线程的提出 4、线程与多线程 5、c/c++程序运行时的内存结构 二、线程之间的同步(机制) (1)临界区(critical section) (2)互斥量(mutexe) (3)信号量(semaphore) 三、API、RTL和MFC对多线程的支持 1、Windows的API函数(CreateThread) 2、MFC中线程创建的MFC函数(AfxBeginThread) 3、MS对C Runtime库的扩展SDK函数(_beginthreadex) 四、实例

线程与主线程


线程:进程内部的一个执行单元,它是程序中一个单 一的顺序控制流程。 系统创建好进程后,实际上就启动执行了该进程的主 执行线程,主执行线程以函数地址形式,比如说main 或WinMain函数,将程序的启动点提供给Windows系 统。主执行线程终止了,进程也就随之终止。 每一个进程至少有一个主执行线程,它无需由用户去 主动创建,是由系统自动创建的。
而不是进程为调度对象效率更高:由于创建新进程必须加载代码,而
线程要执行的代码已经被映射到进程的地址空间,所以创建、执行线 程的速度比进程更快。
相关主题