ImageVerifierCode 换一换
格式:DOCX , 页数:23 ,大小:72.84KB ,
资源ID:4381014      下载积分:2 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/4381014.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(多线程1.docx)为本站会员(b****6)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

多线程1.docx

1、多线程1多线程(Multi-Thread)一、 程序、进程、线程的概念1、程序(Program) 程序是一个具体的文件,是计算机指令的集合,存储在磁盘上,如EXE文件。2、进程(Process) 进程:是一个正在运行程序的实例,是程序在其自身的地址空间中的一次执行活动。 进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,程序不占用系统的运行资源。 进程由两个部分组成:内核对象:操作系统用它来管理进程。是系统用来存放进程的统计信息的地方。地址空间:包含所有可执行模块或DLL模块的代码和数据,以及动态内存所分

2、配的空间,如堆空间和栈空间。 进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程地址空间中的代码。 单个进程可能包含若干个线程,这些线程都“同时” 执行进程地址空间中的代码。 每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当操作系统创建一个进程时,会自动创建这个进程的第一个线程,称为主线程。此后,主线程可以创建其他的线程。如main()、WinMain()所在的线程一般就是主线程。 系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个地址空间是4GB。 每个进程有它自己的私有地址空间

3、。3、线程(Thread) 线程也由两个部分组成:内核对象:操作系统用它来管理线程。是系统用来存放线程的统计信息的地方。线程堆栈:它用于维护线程在执行代码时需要的所有参数和局部变量。 当创建线程时,系统创建一个线程的内核对象。该内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。该数据结构保存了线程的相关统计信息。 线程总是在某个进程中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存,以及同一进程中的所有其他线程的堆栈。这使得单个进程中的多个线程能够非常容易地互相通信。

4、线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。 因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。 操作系统为每一个运行的线程分配一定的CPU时间-时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。 Sleep()函数会主动暂停当前线程的时间片,暂时交出控制权,自己去“睡觉”。 如果计算机拥有多个CPU,多个线程就能真正意义上同时运行了。 使同一进程中的各线程协调一致地工作称为线程的同步。系统提供了多种同步方法,如:临界区(Critical

5、Section),事件(Event),互斥量(Mutex),信号量(Semaphore)等。 可以使用PostThreadMessage()函数进行线程间的通讯。4、使用多线程的场合 帮助理解:一个理发师要为ABC三位贵宾理发,为了不使三个贵宾感到自己有先后之分,理发师可以为A服务n秒,之后为B服务n秒,再为C服务n秒,然后再为A服务n秒,如此循环;只要n足够小,ABC就感觉到自己没有被怠慢。如果有3个理发师,当然是最理想的了,可以每个理发师真正的为一个贵宾服务。这里的理发师就相当于CPU,一个理发师就是单CPU,三个理发师就是多CPU了;而为三个贵宾理发,就是3个工作任务。 QQ多人同时聊天

6、。 火车站多窗口售票。 大批量文件复制:复制文件本身使用一个线程,显示进度使用一个线程。如果用一个线程的话,则主界面会失去反应,给用户感觉是死机了。二、 Win32线程函数Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面选取一些重要函数进行说明。2.1、创建线程HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, /安全属性,一般NULL DWORD dwStackSize, /栈的大小,一般设为0 LPTHREAD_START_ROUTINE lpStartAddress, /线

7、程入口函数指针 LPVOID lpParameter, /传递给线程函数的参数 DWORD dwCreationFlags, /创建后挂起或立即执行 LPDWORD lpThreadId /线程的ID,一般NULL);该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄。各参数说明如下: lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL; dwStackSize:指定了线程的堆栈深度,一般都设置为0; lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起

8、始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名,其原型如下:DWORD WINAPI ThreadProc(LPVOID lpParameter /线程创建者传递给线程的参数); lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数; dwCreationFlags:控制线程创建后的状态,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果为CREATE_SUSPENDED,则创建线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread()被调用; lpThr

9、eadId:该参数返回所创建线程的ID,一般设为NULL;2.2、挂起线程DWORD SuspendThread(HANDLE hThread);该函数用于挂起参数hThread指定的线程,如果函数执行成功,则线程的执行被终止。2.3、唤醒线程DWORD ResumeThread(HANDLE hThread);该函数用于唤醒参数hThread指定的线程,结束该线程的挂起状态,并开始执行该线程。2.4、结束线程VOID ExitThread(DWORD dwExitCode); 该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。参数dwExitCode用来设置线程的退出码。只能在线程

10、内部调用该函数,谁调用就会结束谁。2.5、终止线程BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);一般情况下,线程运行结束之后,线程函数会正常返回,但是应用程序可以调用该函数来强行终止某一线程的执行。各参数含义如下:hThread:将被终结的线程的句柄;dwExitCode:用于指定线程的退出码。2.6、关闭线程句柄BOOL CloseHandle(HANDLE hObject);关闭一个已经打开对象的句柄,用在这里,指关闭一个线程的句柄。注意,只是关闭句柄,并不是关闭线程,线程依然在运行中。只是调用本函数的线程不再需要对该线程进行操

11、作,放弃对它的控制,使线程的内部计数减一,当线程的内部计数为0时,线程会自动关闭。Here 示例0:讲解0MultiThread工程,理解线程的各个步骤。2.7、设置线程的优先级BOOL SetThreadPriority(HANDLE hThread, int nPriority); 各参数含义如下:hThread:线程的句柄;nPriority:指定线程的优先级,如THREAD_PRIORITY_NORMAL等。2.8、C运行时的线程创建C运行时是在Windows操作系统尚未面世时就已经存在的一套C语言的函数库,因为当时并未考虑到多线程的情况,所以在Windows操作系统下用CreateT

12、hread创建的线程中调用了某些C运行时函数,如asctime等,则有可能出现问题,为此后来特地增加了CreateThread的C运行时版本_beginthreadex,原型为:unsigned int _beginthreadex( void *security, unsigned stack_size, unsigned ( _stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr);2.9、示例1:火车站售票系统(“1Mutex”工程)创建一个MFC对话框工程Mult

13、iThread1,界面布局如下图所示:映射2个编辑框为变量m_str1和m_str2,代码放在MultiThread1Dlg.cpp的最下方,如下:int nTickets;HANDLE hThread1;HANDLE hThread2;DWORD WINAPI ThreadProc1(LPVOID lpParameter) CMultiThread1Dlg *pDlg = (CMultiThread1Dlg *)lpParameter; while (TRUE) if (nTickets 0) /:Sleep(1); CString str; str.Format(%d, nTickets)

14、; pDlg-m_str1 += str + rn; /如果刚好在此时本线程的时间片用完了 nTickets-; /票号就不会-1,而是去执行线程2,于是就重票了。 else break; return 0;DWORD WINAPI ThreadProc2(LPVOID lpParameter) CMultiThread1Dlg *pDlg = (CMultiThread1Dlg *)lpParameter; while (TRUE) if (nTickets 0) /:Sleep(1); CString str; str.Format(%d, nTickets); pDlg-m_str2 +

15、= str + rn; /如果刚好在此时本线程的时间片用完了 nTickets-; /票号就不会-1,而是去执行线程2,于是就重票了。 else break; return 0;void CMultiThread1Dlg:OnBtnStart() nTickets = 30; m_str1 = ; m_str2 = ; hThread1 = :CreateThread(NULL, 0, ThreadProc1, this, 0, NULL); hThread2 = :CreateThread(NULL, 0, ThreadProc2, this, 0, NULL); /hThread2 = :

16、CreateThread(NULL, 0, ThreadProc2, this, CREATE_SUSPENDED, NULL); :CloseHandle(hThread1); /这并不会关闭线程,只是这里不再需要对线程进行 :CloseHandle(hThread2); /操作,放弃对它们的控制,使线程的内部计数减一。 Sleep(1000); this-UpdateData(FALSE);点击“开始售票”,发现2个窗口都进行了售票,说明多线程代码都进行工作了。将上述创建线程2的代码倒数第二个参数改成CREATE_SUSPENDED后运行,2号窗口不再售票。三、线程同步上述程序有个明显的问

17、题:一票多售。例如:如果在线程1售票到20号后,已经打印出了票号,但还没有执行到nTickets - -语句时,线程1的时间片刚好用完了,线程2得到运行权,但此时nTickets还是20号,于是售出20号票。这样就出现了一票多售的情况。因此,多线程编程有时需要处理同步问题。要同步多个线程,可以使用临界区(CriticalSection),互斥量(Mutex),事件(Event),信号量(Semaphore)、互锁函数等。临界区非常适合于在同一个进程内部以序列化的方式访问共享的数据。然而,有时用户希望一个线程与其他线程执行的某些操作取得同步,这就需要使用内核对象来同步线程。常用的内核对象有互斥量

18、、事件和信号量,其他的还包括文件、控制台输入、文件变化通知、可等待的计时器。每一个内核对象在任何时候都处于两种状态之一:信号态(signaled)和无信号态(nonsignaled)。线程在等待其中的一个或多个内核对象时,如果内核对象处于无信号态,线程自身将被系统挂起,直到等待的内核对象变为有信号状态时,线程才恢复运行。常用的等待函数有2个:WaitForSingleObject和WaitForMultipleObjectsDWORD WaitForSingleObject( /等待单个内核对象 HANDLE hHandle, /指向内核对象的句柄 DWORD dwMilliseconds /

19、等待的毫秒数,如果为INFINITE,则无限期等待。);WaitForSingleObject函数返回值返回值含义WAIT_OBJECT_0对象处于有信号状态WAIT_TIMEOUT对象在指定时间内没有变为有信号状态WAIT_ABANDONED对象是一个互斥量,由于被放弃了而变为有信号状态WAIT_FAILED发生了错误。调用GetLastError可以得到详细的错误信息DWORD WaitForMultipleObjects(/等待多个对象 DWORD nCount, /对象的个数 CONST HANDLE *lpHandles,/对象句柄数组 BOOL bWaitAll, /是否要等到所有

20、的对象都变为信号态 DWORD dwMilliseconds /等待的毫秒数,如果为INFINITE,则无限期等待。)3.1、使用互斥量(Mutex)同步多线程相关函数有CreateMutex,ReleaseMutex,WaitForSingleObject等。3.1.1、创建互斥量HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName);互斥量能够同步多个进程间的数据访问。各参数说明如下: lpMutexAttributes:指向一个 SECURITY_ATTRI

21、BUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL; bInitialOwner:BOOL类型,如果为真,则创建该互斥量的线程获得该对象的所有权,否则,该线程不获得其所有权。 lpName:互斥量对象的名称。如果为NULL,则创建一个匿名的互斥对象。如果不为空,则在函数调用成功后,调用GetLastError函数将会返回ERROR_ALREADY_EXISTS,可以利用该特性阻止一个进程多次启动。3.1.2、释放互斥量BOOL ReleaseMutex(HANDLE hMutex);访问完共享资源后,利用该函数释放对互斥量的控制权。3.1.3、请求互斥量的使用权,进而锁定对

22、共享资源的访问。DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); 参数含义: hHandle:所请求的互斥量对象的句柄;dwMilliseconds:指定等待时间,单位为毫秒。一般设为INFINITE,表示无限等待。该函数调用后,会一直等到参数dwMilliseconds指定的时间已过,或者在该时间内,互斥对象变成了有信号状态,函数才会返回。返回值:WAIT_OBJECT_0:所请求的对象是有信号状态WAIT_TIMEROUT:指定的时间以过,并且所请求的对象是无信号状态。WAIT_ABANDONED:所请求的对象

23、是一个互斥对象,并且掀起拥有该对象的线程在终止前没有释放该对象,这时,该对象的所有权将授予当前调用线程,并且该互斥对象被置为无信号状态。3.1.4、使用互斥量同步方法修改上述代码,结果完全正常了。代码如下,红色部分为新添加的代码:int nTickets;HANDLE hThread1;HANDLE hThread2;HANDLE hMutex;DWORD WINAPI ThreadProc1(LPVOID lpParameter) CMultiThread1Dlg *pDlg = (CMultiThread1Dlg *)lpParameter; while (TRUE) :WaitForSi

24、ngleObject(hMutex, INFINITE); if (nTickets 0) /:Sleep(1); CString str; str.Format(%d, nTickets); pDlg-m_str1 += str + rn; /如果刚好在此时本线程的时间片用完了 nTickets-; /票号就不会-1,而是去执行线程2,于是就重票了。 :ReleaseMutex(hMutex); else :ReleaseMutex(hMutex); break; return 0;DWORD WINAPI ThreadProc2(LPVOID lpParameter) CMultiThre

25、ad1Dlg *pDlg = (CMultiThread1Dlg *)lpParameter; while (TRUE) :WaitForSingleObject(hMutex, INFINITE); if (nTickets 0) /:Sleep(1); CString str; str.Format(%d, nTickets); pDlg-m_str2 += str + rn; /如果刚好在此时本线程的时间片用完了 nTickets-; /票号就不会-1,而是去执行线程2,于是就重票了。 :ReleaseMutex(hMutex); else :ReleaseMutex(hMutex);

26、break; return 0;void CMultiThread1Dlg:OnBtnStart() nTickets = 30; m_str1 = ; m_str2 = ; hThread1 = :CreateThread(NULL, 0, ThreadProc1, this, 0, NULL); hThread2 = :CreateThread(NULL, 0, ThreadProc2, this, 0, NULL); /hThread2 = :CreateThread(NULL, 0, ThreadProc2, this, CREATE_SUSPENDED, NULL); hMutex

27、= :CreateMutex(NULL, FALSE, NULL);:ReleaseMutex(hMutex); /主线程并不需要hMutex的控制权,释放掉 :CloseHandle(hThread1); /这并不会关闭线程,只是这里不再需要对线程进行 :CloseHandle(hThread2); /操作,放弃对它们的控制,使线程的内部计数减一。 Sleep(1000); this-UpdateData(FALSE); :CloseHandle(hMutex);3.1.5、使用命名的互斥量阻止进程多次运行BOOL CMultiThread1App:InitInstance() HANDLE

28、 hMutex = :CreateMutex(NULL, FALSE, tickets); if (hMutex & ERROR_ALREADY_EXISTS = :GetLastError() return FALSE; :ReleaseMutex(hMutex); :CloseHandle(hMutex);/3.2、使用事件(Event)同步多线程与互斥量和信号量不同,互斥量和信号量用于控制对共享数据的访问,而事件发送信号表示某一操作已经完成。有两种事件对象:手动重置事件和自动重置事件。手动重置事件用于同时向多个线程发送信号;自动重置事件用于向一个线程发送信号。如果有多个线程调用WaitF

29、orSingleObject或者WaitForMultipleObjects等待一个自动重置事件,那么当该自动重置事件变为信号态时,其中的一个线程会被唤醒,被唤醒的线程开始继续运行,同时自动重置事件又被置为无信号态,其他线程依旧处于挂起状态。从这一点看,自动重置事件有点类似于互斥量。手动重置事件不会被WaitForSingleObject和WaitForMultipleObjects自动重置为无信号态,需要调用相应的函数才能将手动重置事件重置为无信号态。因此,当手工重置事件有信号时,所有等待该事件的线程都将被激活。事件对象使用CreateEvent函数创建:HANDLE CreateEvent

30、( LPSECURITY_ATTRIBUTES lpEventAttributes, /安全属性,一般为NULL BOOL bManualReset, /是手动重置吗? BOOL bInitialState, /初始化成有信号态吗? LPCTSTR lpName /名称,为NULL则匿名);参数bManualReset为TRUE时,指定创建的是手动重置事件,否则为自动重置事件;参数bInitialState表示事件对象被初始化时是信号态还是无信号态;参数lpName指定事件对象的名称,其他进程中的线程可以通过该名称调用CreateEvent或者OpenEvent函数得到该事件对象的句柄。HANDLE

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1