今天重新看了孙鑫MFC的多线程那一课,感觉学了JAVA再看这一课收获颇多。
当初实训时候看了很久只是知道怎么抄代码……
- 为什么要使用多线程?
顾名思义,多线程就是就是可以让程序同时跑多段代码。但如果你的CPU是单核的,那对不起,学过硬件基础可以知道,这就是资源相关,一个CPU不能同时跑多段代码。所以代码只能轮流执行,但给人感觉是同时执行的。
在多核情况下,多线程一般比单线程快。当然也有例外,比如用N个线程做文件IO的事情,由于IO一直负荷工作,再者线程的切换也需要代价,所以用N个线程做同一件事情不见得比单线程更优秀。在CSDN论坛里,我见过一个很形象的比喻:厕所只有一个,几个人并发抢厕所,最后也只有一个人能在里边,其他人都要乖乖在外边等。
所以,为了效率,多线程要用在做不同的事情上。比如用一个线程做IO,一个线程做数据的计算,一个线程做通信…多线程要用在复杂的事情上才能显示出它的优势。
- 线程的创建
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
- lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
- dwStackSize:指定了线程的堆栈深度,一般都设置为0;
- lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;
- lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
- dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
- lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回NULL。
这里说下LPVOID lpParameter这个参数。如果想传入多个参数,要定义一个全局的结构体指针。赋值后把指针传入,在线程函数里在把LPVOID强制转化成该结构体指针即可。
VC这里的多线程跟JAVA不同。在JAVA中,主线程结束了,其他线程还能继续执行。但是在VC中,main函数退出了,其他线程就不再继续执行了。
如果线程函数不需传参,可以这样创建:
HANDLE hThread = CreateThread(NULL,0,FunProc,NULL,0,NULL);//被创建后立即执行。
如果在之后的程序中不适用该线程的引用
CloseHandle(hThread);是一个很好的习惯。
该函数可以使线程的内核对象计数-1.这样,当线程结束时,就可以释放该线程的内核对象。否则要等待进程结束时才能释放。
- Sleep函数
操作系统为每一个运行线程安排一定的CPU时间——时间片。如果main函数耗时很少,在时间片内执行完了,此时线程的函数都得不到执行。那怎么办呢?
有两个方法:
- 用while(condition) 做空循环。
- 用Sleep()函数。
考虑到main函数做空循环也是需要付出代价的,所以用Sleep显得更有效率。该函数传入的时间是以毫秒为单位。
- 互斥对象
如果多个线程都可以对一个资源进行读写,那怎么保证不出差错呢?
在学JAVA时,老师讲过一个很经典的例子。
JIM和MARY共用一个信用卡账户,他们约定不让账户的钱少于0。一天JIM去取钱,发现金额为500,很开心,但是他突然睡着了。很不巧的是MARY也去取钱,此时金额一定是500,MARY取了500。等JIM醒来,他也取了500,此时账户上的钱是-500了。为了防止此类事情发生,我们需要一个锁,用这个锁来锁住这个账户。
用CreateMutex可以创造这种锁:
HANDLE hMutex=CreateMutex(NULL,TRUE,NULL);
第一个参数: 一般为NULL,用缺省的安全性。
第二个参数: 创建进程希望立即拥有互斥体,则设为TRUE,否则为FALSE。一个互斥体同时只能由一个线程拥有。
第三个参数NULL,匿名的互斥对象。
一个线程可以多次拥有一个互斥对象,这种功能是靠计数器维护的。
而WaitForSingleObject(hMutex,INFINITE);可以使计数器+1;
ReleaseMutex(hMutex);可以使计数器-1。
以上的两句代码市场嵌套在要保护的代码中。注意,不能把两句代码写在不同的函数里,应该在哪里申请到互斥对象就在哪里释放。
当然如果我们不写ReleaseMutex(hMutex);等到线程结束后,系统也会为我们做类似的工作。
如果在main函数中HANDLE hMutex=CreateMutex(NULL,TRUE,NULL); 那么main函数就得到了互斥对象,那么加了锁的线程就得不到访问的机会。所以要ReleaseMutex(hMutex)。
其实在CreateMutex(NULL,TRUE,NULL); 之后,也可以写WaitForSingleObject(hMutex,INFINITE);
不过这时候计数器就为2了,所以要ReleaseMutex(hMutex)两次才能达到之前的效果。
- 命名的互斥对象
CreateMutex的第三个参数为互斥对象的名字,如果给他命名,我们就可以用它来判断我们实例化了几个程序。
参考代码如下:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/36826.html