Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1405496
  • 博文数量: 120
  • 博客积分: 182
  • 博客等级: 入伍新兵
  • 技术积分: 2278
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-19 16:31
文章分类

全部博文(120)

文章存档

2015年(12)

2014年(13)

2013年(40)

2012年(55)

分类: C/C++

2013-10-25 18:51:06

     进程是系统中的重要概念,简单来说字面的意思就是一个运行中的程序,但是程序代表的是静态的指令代码。进程由系统管理的内核对象和存放程序运行资源的地址空间组成。内核对象由系统管理,因此应用程序是无法直接访问的;地址空间中则包含着程序运行所需的所有资源,如可执行模块、DLL、代码和数据,以及动态分配的栈与堆。可以说,其实进程就是程序运行的资源的容器。但是进程只是为程序的执行提供了一个场所,真正实现执行流程的是线程。每个进程启动时都会自动建立一个线程作为主线程,由它去创建其他的线程。线程与进程相比更为轻量级,二者的主要联系在于:
1. 线程创建在进程的地址空间中,对于进程资源有着完全访问权限,多线程间共享资源,可自由通信;
2. 线程建立时也有自己的内核对象和线程栈,而非地址空间;
3. 一般需要实现多任务时我们更推荐使用线程实现,因为创建一个进程需要分配地址空间,而且进程间的切换也很不方便,即使用进程开销很高;

一、创建多线程
     简单了解了进程与线程的概念之后,我们来看看如何在程序中创建线程。Windows SDK为我们提供了创建线程的专用函数:CreateThread()

点击(此处)折叠或打开

  1. HANDLE CreateThread(
  2.   LPSECURITY_ATTRIBUTES lpsa,
  3.   DWORD cbStack,
  4.   LPTHREAD_START_ROUTINE lpStartAddr,
  5.   LPVOID lpvThreadParam,
  6.   DWORD fdwCreate,
  7.   LPDWORD lpIDThread
  8. );
-1-第一个参数是安全属性结构,主要控制该线程句柄是否可为进程的子进程继承使用,默认使用NULL时表示不能继承;若想继承线程句柄,则需要设置该结构体,将结构体的bInheritHandle成员初始化为TRUE;
-2-cbStack表示的线程初始栈的大小,若使用0则表示采用默认大小初始化;
-3-lpStartAddr表示线程开始的位置,即线程要执行的函数代码,这点有点类似于回调函数的使用;
-4-lpvThreadParam用来接收线程过程函数的参数,不需要时可以设置为NULL;
-5-fdwCreate表示创建线程时的标志,CREATE_SUSPENDED表示线程创建后挂起暂不执行,必须调用ResumeThread才可以执行,0表示线程创建之后立即执行
-6-lpIDThread用来保存线程的ID;
     了解了CreateThread函数的用法,我们来看一个例子实际体验一下。下面的例子会开启一个线程循环输出信息,我们可以查看结果:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter);


  7. int main()
  8. {

  9.     int j = 0;
  10.     
  11.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  12.     CloseHandle(hThread_1);
  13.     
  14.     while (j++ < 1000)
  15.      cout << "MainThread is running for" << " the "<< j <<" times "<<endl;
  16.         
  17.     system("pause");
  18.     return 0;
  19.         
  20.     
  21. }


  22. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  23. {
  24.       int i = 0;
  25.       while (i++ < 1000)
  26.           cout << "Thread 1 is running for" <<" the "<< i <<" times "<<endl;
  27.        
  28.       return 0;
  29.       }
      由于我们使用system("pause")命令,因此我们不需要Sleep()命令同样可以看到主线程和分线程的交替执行:



二、多线程的问题
     上面的程序我们只是建立一个新线程,如果建立两个呢?下面我们模拟一个火车售票的模型,线程1和线程2同时负责售票,主线程负责平台搭建。线程1和线程2分别访问全局变量tickets,输出其值作为票号然后将其值减一,直至“售完”所有票:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter);
  7. DWORD WINAPI Fun2Proc(LPVOID lpParameter);

  8. int tickets = 100;

  9. int main()
  10. {
  11.            
  12.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  13.     HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
  14.     CloseHandle(hThread_1);
  15.     CloseHandle(hThread_2);
  16.            
  17.     system("pause");
  18.     return 0;
  19.         
  20.     
  21. }


  22. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  23. {
  24.      
  25.       while (true)
  26.       {        
  27.             if (tickets > 0)
  28.             {
  29.                  Sleep(10);
  30.                  cout << "Thread 1 sell ticket : "<<tickets--<<endl;
  31.                  }
  32.             else
  33.                  break;       
  34.             }
  35.       return 0;
  36.       }
  37.       
  38. DWORD WINAPI Fun2Proc(LPVOID lpParameter)
  39. {

  40.        while (true)
  41.       {
  42.         
  43.             if (tickets > 0)
  44.             {
  45.                  Sleep(10);
  46.                  cout << "Thread 2 sell ticket : "<<tickets--<<endl;
  47.                  }
  48.             else
  49.                  break;
  50.          
  51.             }
  52.       return 0;
  53.       }
      再次运行之后结果:

     分析下上面的结果,首先线程1和线程2确实交替运行“购票”了,其次看到不规则的输出,1314连到一起。关于这点我们要知道由于CPU是执行分片处理的,即不同的线程会得到不同的时间片来执行程序,尤其是对于单CPU来说更是如此,因此当线程1执行到"Thread 1 sell ticket"时结束分片,由线程2继续执行;线程2执行到同样位置再次交还给线程1继续执行显示“13”,然后线程2执行显示“14”。这样看好像也没有问题,但是问题在于最后出现了“0”,我们的代码中是不允许出现0的,之所以这样,同上面的元婴一样,在线程1执行到ticket = 1时交接给了线程2执行,并且输出了其值1,然后线程1继续输出了当前值0.此时的if条件就失去作用了。虽然这种情况不一定总是发生,但是在实际的操作中是很有可能出现的,而且排查起来也很困难。那么我们该如何解决呢?
     问题的关键在于线程1和线程2同时访问了全局变量tickets,导致了错误;那么我们的解决方案就是将全局变量的访问控制起来就可以了,不允许同时有多个线程同时访问该变量。我们使用互斥量来实现。


三、互斥量的应用
     使用互斥量并不困难,核心步骤如下:
-1-CreateMutex创建一个互斥量:

点击(此处)折叠或打开

  1. HANDLE CreateMutex(
  2.   LPSECURITY_ATTRIBUTES lpMutexAttributes,
  3.   BOOL bInitialOwner,
  4.   LPCTSTR lpName
  5. );
      第一个参数同样是安全结构,默认是NULL不能继承句柄;第二个参数为FALSE时创建Mutex时不指定所有权,若为TRUE则指定为当前的创建线程ID为所有者,其他线程访问需要先ReleaseMutex;第三个参数用于设置Mutex名,后续我们会说明,为NULL时表示是匿名互斥量。
-2-WaitForSingleObject():请求一个互斥量的访问权;
-3-ReleaseMutex():释放一个互斥量的访问权;
     好了,我们再来看看应用了互斥量的改进程序:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter);
  7. DWORD WINAPI Fun2Proc(LPVOID lpParameter);

  8. int tickets = 100;
  9. HANDLE hMutex;

  10. int main()
  11. {
  12.    

  13.     hMutex = CreateMutex(NULL, FALSE, NULL);
  14.     
  15.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  16.     HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
  17.     CloseHandle(hThread_1);
  18.     CloseHandle(hThread_2);

  19.         
  20.     system("pause");
  21.     return 0;
  22.         
  23.     
  24. }


  25. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  26. {

  27.       while (true)
  28.       {
  29.             WaitForSingleObject(hMutex, INFINITE);
  30.             if (tickets > 0)
  31.             {
  32.                  Sleep(10);
  33.                  cout << "Thread 1 sell ticket : "<<tickets--<<endl;
  34.                  }
  35.             else
  36.                  break;
  37.             ReleaseMutex(hMutex);
  38.             }
  39.       return 0;
  40.       }
  41.       
  42. DWORD WINAPI Fun2Proc(LPVOID lpParameter)
  43. {

  44.        while (true)
  45.       {
  46.             WaitForSingleObject(hMutex, INFINITE);
  47.             if (tickets > 0)
  48.             {
  49.                 Sleep(10);
  50.                  cout << "Thread 2 sell ticket : "<<tickets--<<endl;
  51.                  }
  52.             else
  53.                  break;
  54.             ReleaseMutex(hMutex);
  55.             }
  56.       return 0;
  57.       }
      这次运行之后的结果不会出现上面的错误了:

      互斥量还有一个小应用,利用命名互斥量来保证只有一个程序实例运行。我们可以创建一个命名互斥量,当程序要重复运行时,检查互斥量的返回值,若为ERROR_ALREADY_EXISTS则表示已经有一个实例运行了,直接return即可。在源程序中添加以下代码:

点击(此处)折叠或打开

  1. //确保只有一个实例运行
  2.     HANDLE hMutex_1 = CreateMutex(NULL, TRUE, "tickets");
  3.     if (hMutex_1)
  4.     {
  5.                if (ERROR_ALREADY_EXISTS == GetLastError())
  6.                {
  7.                        cout << "Only one instance can run !" << endl;
  8.                        system("pause");
  9.                        return 0;
  10.                                         }
  11.                }










阅读(5274) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~