引用 C++ 多线程实现(转)

 

引用

charles_yangC++ 多线程实现(转)
C/C++ co de
    
    
// 这是2个线程模拟卖火车票的小程序 #include < windows.h > #include < iostream.h > DWORD WINAPI Fun1Proc(LPVOID lpParameter); // thread data DWORD WINAPI Fun2Proc(LPVOID lpParameter); // thread data int index = 0 ; int tickets = 10 ; HANDLE hMutex; void main() { HANDLE hThread1; HANDLE hThread2; // 创建线程 hThread1 = CreateThread(NULL, 0 ,Fun1Proc,NULL, 0 ,NULL); hThread2 = CreateThread(NULL, 0 ,Fun2Proc,NULL, 0 ,NULL); CloseHandle(hThread1); CloseHandle(hThread2); // 创建互斥对象 hMutex = CreateMutex(NULL,TRUE, " tickets " ); if (hMutex) { if (ERROR_ALREADY_EXISTS == GetLastError()) { cout << " only one instance can run! " << endl; return ; } } WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); ReleaseMutex(hMutex); Sleep( 4000 ); } // 线程1的入口函数 DWORD WINAPI Fun1Proc(LPVOID lpParameter) // thread data { while ( true ) { ReleaseMutex(hMutex); WaitForSingleObject(hMutex,INFINITE); if (tickets > 0 ) { Sleep( 1 ); cout << " thread1 sell ticket : " << tickets --<< endl; } else break ; ReleaseMutex(hMutex); } return 0 ; } // 线程2的入口函数 DWORD WINAPI Fun2Proc(LPVOID lpParameter) // thread data { while ( true ) { ReleaseMutex(hMutex); WaitForSingleObject(hMutex,INFINITE); if (tickets > 0 ) { Sleep( 1 ); cout << " thread2 sell ticket : " << tickets --<< endl; } else break ; ReleaseMutex(hMutex); } return 0 ; }
   
   
调用C r e a t e P r o c e s s函数时如何创建进程的主线程。如果想要创建一个或多个辅助函数,只需要让一个已经在运行的线程来调用C r e a t e T h r e a d:


HANDLE CreateThread(
  PSECURITY_ATTRIBUTES psa,
  DWORD cbStack,
  PTHREAD_START_ROUTINE pfnStartAddr,
  PVOID pvParam,
  DWORD fdwCreate,
  PDWORD pdwThreadID);
当C r e a t e T h r e a d被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。这与进程和进程内核对象之间的关系是相同的。
系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。

注意C r e a t e T h r e a d函数是用来创建线程的Wi n d o w s函数。不过,如果你正在编写C / C + +代码,决不应该调用C r e a t e T h r e a d。相反,应该使用Visual C++运行期库函数_ b e g i n t h r e a d e x。如果不使用M i c r o s o f t的Visual C++编译器,你的编译器供应商有它自己的C r e a t e T h r e d替代函数。不管这个替代函数是什么,你都必须使用。本章后面将要介绍_ b e g i n t h r e a d e x能够做什么,它的重要性何在。

这就是Create Thread函数的概述,下面各节将要具体介绍C r e a t e T h r e a d的每个参数。

6.4.1 psa

p s a参数是指向S E C U R I T Y _ AT T R I B U T E S结构的指针。如果想要该线程内核对象的默认安全属性,可以(并且通常能够)传递N U L L。如果希望所有的子进程能够继承该线程对象的句柄,必须设定一个S E C U R I T Y _ AT T R I B U T E S结构,它的b I n h e r i t H a n d l e成员被初始化为T R U E。详细信息参见第3章。

6.4.2 cbStack

c b S t a c k参数用于设定线程可以将多少地址空间用于它自己的堆栈。每个线程拥有它自己的堆栈。当C r e a t e P r o c e s s启动一个进程时,它就在内部调用C r e a t e T h r e a d来对进程的主线程进行初始化。对于c b S t a c k参数来说,C r e a t e P r o c e s s使用存放在可执行文件中的一个值。可以使用链接程序的/ S TA C K开关来控制这个值:


/STACK:[reserve] [,commit]
r e s e r v e参数用于设定系统应该为线程堆栈保留的地址空间量。默认值是1 MB。C o m m i t参数用于设定开始时应该承诺用于堆栈保留区的物理存储器的容量。默认值是1页。当线程中的代码执行时,可能需要多个页面的存储器。当线程溢出它的堆栈时,就生成一个异常条件(关于线程堆栈和堆栈溢出的异常条件的详细说明,参见第1 6章,关于一般异常条件的处理的详细说明,参见第2 3章)。系统抓取该异常条件,并且将另一页(或者你为c o m m i t参数设定的任何值)用于保留空间,这使得线程的堆栈能够根据需要动态地扩大。
当调用C r e a t e T h r e a d时,如果传递的值不是0,就能使该函数将所有的存储器保留并分配给线程的堆栈。由于所有的存储器预先作了分配,因此可以确保线程拥有指定容量的可用堆栈存储器。保留空间的容量既可以是/ S TA C K链接程序设定的容量,也可以是C b S t a c k的值,谁大就用谁。分配的存储器容量应该与传递的c b S t a c k值相一致。如果将0传递给C b S t a c k参数,C r e a t e T h r e a d就保留一个区域,并且将链接程序嵌入. e x e文件的/ S TA C K链接程序开关信息指明的存储器容量分配给线程堆栈。

保留空间的容量用于为堆栈设置一个上限,这样就可以抓住代码中的循环递归错误。例如,你编写一个递归自调用函数,该函数也包含导致循环递归的一个错误。每次函数调用自己的时候,堆栈上就创建一个新的堆栈框。如果系统不设定堆栈的最大值,该递归函数就永远不会停止对自己的调用。进程的所有地址空间将被分配,大量的物理存储器将被分配给该堆栈。通过设置一个堆栈限制值,就可以防止应用程序用完大量的物理存储器,同时,也可以更快地知道何时程序中出现了错误(第1 6章中的S u m m a t i o n示例应用程序显示了如何跟踪和处理应用程序中的堆栈溢出)。

6.4.3 pfnStartAddr和pvParam

p f n S t a r t A d d r参数用于指明想要新线程执行的线程函数的地址。线程函数的p v P a r a m参数与原先传递给C r e a t e T h r e a d的p v P a r a m参数是相同的。C r e a t e T h r e a d使用该参数不做别的事情,只是在线程启动执行时将该参数传递给线程函数。该参数提供了一个将初始化值传递给线程函数的手段。该初始化数据既可以是数字值,也可以是指向包含其他信息的一个数据结构的指针。

创建多个线程,使这些线程拥有与起始点相同的函数地址,这是完全合乎逻辑的并且是非常有用的。例如,可以实现一个We b服务器,以便创建一个新线程来处理每个客户机的请求。每个线程都知道它正在处理哪个客户机的请求,因为当创建线程时,你传递了一个不同的p z P a r a m值。

记住,Wi n d o w s是个抢占式多线程系统,这意味着新线程和调用C r e a t e T h r e a d的线程可以同时执行。由于线程可以同时运行,就会出现一些问题。请看下面的代码:


DWORD WINAPI FirstThread(PVOID pvParam)
{
  //Initialize a stack-based variable
  int x = 0;
  DWORD dwThreadID;

  //Create a new thread.
  HANDLE hThread = CreateThread(NULL, 0, SecondThread,
      (PVOID)&x, 0, &dwThreadId);

  //We don't reference the new thread anymore,
  //so close our handle to it.
  closeHandle(hThread);

  //Our thread is done.
  //BUG:our stack will be destroyed,
  //but SecondThread might try to access it.
  return(0);
}


DWORD WINAPI SecondThread(PVOID pvParam)
{
  //Do some lengthy processing here.
  ...
 
  //Attempt to access the variable on FirstThread's stack.
  //NOTE:This may cause an access violation - it depends on timing!
  *((int *) pvParam) = 5;
  ...
 
  return(0);
}
在上面这个代码中,F i r s t T h r e a d可以在S e c o n d T h r e a d将5分配给F i r s t T h r e a d的x之前结束它的操作。如果出现这种情况,S e c o n d T h r e a d将不知道F i r s t T h r e a d已经不再存在,并且仍然试图修改现在已经是个无效地址的内容。这会导致S e c o n d T h r e a d产生一次访问违规,因为F i r s t T h r e a d的堆栈已经在F i r s t T h r e a d终止运行时被撤消。解决这个问题的方法之一是将x声明为一个静态变量,这样,编译器就为应用程序的数据部分中的x创建一个存储区,而不是在堆栈上创建存储区。
但是这使得函数成为不可重新进入的函数。换句话说,无法创建两个执行相同函数的线程,因为两个线程将共享该静态变量。解决这个问题(和它的更复杂的变形)的另一种方法是使用正确的线程同步技术(第8、9章和1 0章介绍)

6.4.4 fdwCreate

f d w C r e a t e参数可以设定用于控制创建线程的其他标志。它可以是两个值中的一个。如果该值是0,那么线程创建后可以立即进行调度。如果该值是C R E AT E _ S U S P E N D E D,系统可以完整地创建线程并对它进行初始化,但是要暂停该线程的运行,这样它就无法进行调度。

C R E AT E _ S U S P E N D E D标志使得应用程序能够在它有机会执行任何代码之前修改线程的某些属性。由于这种必要性很少,因此该标志并不常用。第5章介绍的J o b L a b应用程序说明了该标志的正确方法。

6.4.5 pdwThreadID

C r e a t e T h r e a d的最后一个参数是p d w T h r e a d I D,它必须是D W O R D的一个有效地址,C r e a t e T h r e a d使用这个地址来存放系统分配给新线程的I D (进程和线程的I D已经在第4章中作了介绍)。

注意在Windows 2000(和Windows NT 4)下,可以(并且通常是这样做的)为该参数传递N U L L。它告诉函数,你对线程的I D不感兴趣,但是线程已经创建了。在Windows 95和Windows 98下,为该参数传递N U L L会导致函数运行失败,因为函数试图将I D写入地址N U L L(这是不合法的)。因此线程不能创建。

当然,操作系统之间的不一致现象会给编程人员带来一些问题。例如,在Wi n d o w s2 0 0 0下(即使为p d w T h r e a d I D参数传递了N U L L,它也创建了该线程)编写和测试了一个应用程序,当后来在Windows 98上运行该应用程序时,C r e a t e T h r e a d将不创建新的线程。必须始终在你声称支持的所有操作系统(和所有版本)上充分测试应用程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值