VC多线程编程基础

来源:互联网 发布:python项目实战 知乎 编辑:程序博客网 时间:2024/05/18 03:22

    今天重新看了孙鑫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函数耗时很少,在时间片内执行完了,此时线程的函数都得不到执行。那怎么办呢?

 

    有两个方法:

  1. 用while(condition) 做空循环。
  2. 用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的第三个参数为互斥对象的名字,如果给他命名,我们就可以用它来判断我们实例化了几个程序。

参考代码如下:

 

原创粉丝点击