C++线程编程解析+事例

1:创建线程函数介绍


在window系统中编写控制台程序,创建线程

使用CreateThread()函数创建,则线程函数必须申明为DWORD WINAPI;

使用_beginthreadex()创建,则线程函数必须申明为unsigned int WINAPI;

并需要设置环境:工程->设置->C/C++->Code Generation->Use run-time libray->选 Debug Multithread(多线程),或 Multithread.

将类成员函数作为线程函数方式:

1.将类成员申明为STATIC成员函数;

2.将函数申明为类的友元函数;


   1.1  CreateThread函数

“CreateThread函数是用来创建线程的Windows函数不过,如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用Visual C++运行期库函数_beginthreadex。如果不使用Microsoft的Visual C++编译器,你的编译器供应商有它自己的CreateThred替代函数。不管这个替代函数是什么,你都必须使用。”

HANDLE CreateThread

( LPSECURITY_ATTRIBUTES lpsa,

 DWORD cbStack, 

 LPTHREAD_START_ROUTINE lpStartAddr,

 LPVOID lpvThreadParam,

 DWORD fdwCreate,

 LPDWORD lpIDThread);

具体解释为:

 //第1个参数:lpsa:线程句柄的安全属性,比如子进程是否可以继承这个线程句柄,一般情况设置为NULL

 //第1个参数:cbStack:线程栈大小,一般取0表示默认大小

 //第3个参数:lpStartAddr:线程入口函数

                 typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) ( void* lpThreadParameter );

                在win32程序中默认的调用函数约定就是WINAPI ,__stdcall = WINAPI 

                因此你可以声明你的入口函数为:DWORD WINAPI ThreadProc( void* lpParamete) {//线程中你要做的事情}

 //第4个参数:lpvThreadParam:就是线程入口函数的参数,就是ThreadProc( void* lpParamete) 的参数

 //第5个参数:fdwCreate:控制线程创建的标志一般为0,表示线程立即启动。如果你想创建之后把线程挂起来可以传入CREATE_SUSPENDED ,传入这个参数你需要再适当的地方调用                              ResumeThread 启动线程

 //第6个参数lpIDThread:是线程ID返回值,这个用来接收线程返回的ID。

   1.2 _beginthreadex()函数

参数说明:

unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );

 //第1个参数:安全属性,NULL为默认安全属性
 //第2个参数:指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
 //第3个参数:指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址)
 //第4个参数:传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
 //第5个参数:线程初始状态,0:立即运行;CREATE_SUSPEND:suspended(悬挂)
 //第6个参数:用于记录线程ID的地址



C运行库_beginthreadex()。他经过一些处理后,再调用CreateThread(),因此比CreateThread安全一些。如果要强制结束的话也最好用_endthreadex 结束,因为他也要一些处理。如果不是调用_endthreadex来终止线程的运行,那么数据块就不会被撤消,内存泄漏就会出现

另外还有AfxBeginThread()

具体区别请参照:http://hi.baidu.com/kings0527/blog/item/11838d02a1b3bbe608fa93ff.html

2:线程创建的例子

以下只是框架 

全局变量  HANDLE g_Thread ;

 在类CA中  

 class CA
{


public :

  int m_member;  //成员变量 
   static WINAPI UINT  threadFunc(LPVOID P); //必须是静态的 或者全局的 
   

   void OnInitDialog()  

{

 g_Thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,this, 0, &threadID);  //this 参数的传递 

if (g_Thread==0)
{
          GetLastError(); 

   .............
            MessageBox(_T("_beginthreadex 创建失败!"));
           _endthread(g_Thread);
        

}

else
MessageBox(_T("_beginthreadex 创建成功!"));

}

UINT threadFunc(LPVOID p)
{
   CA *obj = p;
   obj->m_member ;   //成员变量的传递
}

更好的例子参照:http://blog.csdn.net/maopig/article/details/6772258   详细的解释  


3:线程的同步
 

    1临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

    2互斥量:为协调共同对一个共享资源的单独访问而设计的。

    3信号量:为控制一个具有有限数量用户资源而设计。

    4事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。


  互斥体代码例子:

  
<pre name="code" class="cpp">//这是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())//得到错误:已经存在
        {
		// 如果已有互斥量存在则释放句柄并复位互斥量
 		CloseHandle(m_hMutex);
 		m_hMutex = NULL;
            cout<<"only one instance can run!"<<endl;
            return;
        }
    }
    WaitForSingleObject(hMutex,INFINITE);
    ReleaseMutex(hMutex);//申请了两次就要施放两次(没搞懂在哪里申请了两次?难道建立一次,wait也算一次?)
    ReleaseMutex(hMutex);//谁申请谁施放
    
    Sleep(4000);//让主线程睡4秒,让其他线程有时间执行完他们的代码,如果不睡就会出现其他线程执行不完或出错的情况,但时如果不知道线程需要多少时间执行,那应该写多少时间?
}
//线程1的入口函数
DWORD WINAPI Fun1Proc(LPVOID lpParameter)//thread data
{
    while (true)//无限循环线程
    {
        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)
    {
        WaitForSingleObject(hMutex,INFINITE);
        if (tickets>0)
        {
            Sleep(1);
            cout<<"thread2 sell ticket :"<<tickets--<<endl;
        }
        else
            break;
        ReleaseMutex(hMutex);
    }
    
    return 0;
}
//详解
HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
 BOOL bInitialOwner, // 初始化互斥对象的所有者
 LPCTSTR lpName // 指向互斥对象名的指针
);
参数 
lpMutexAttributes 
指向一个SECURITY_ATTRIBUTES结构的指针,这个结构决定互斥体句柄是否被子进程继承。     
bInitialOwner 
布尔类型,决定互斥体的创建者是否为拥有者 
lpName 
指向互斥体名字字符串的指针。互斥体可以有名字。 
互斥体的好处是可以在进程间共享
http://blog.csdn.net/goodai007/article/details/6976081
临界区例子:


 // Global variable
CRITICAL_SECTION CriticalSection; 

void main()
{
...

// Initialize the critical section one time only.
if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 
0x80000400) ) 
return;
...

// Release resources used by the critical section object.
DeleteCriticalSection(&CriticalSection)
}

DWORD WINAPI ThreadProc( LPVOID lpParameter )
{
...

// Request ownership of the critical section.
EnterCriticalSection(&CriticalSection); 

// Access the shared resource.

// Release ownership of the critical section.
LeaveCriticalSection(&CriticalSection);

...
}



更多例子在:
 VC下线程同步的三种方法(互斥、事件、临界区)
http://blog.csdn.net/pl2597758/article/details/2507790#

首选使用临界区对象,主要原因是使用简单。 
EnterCriticalSection()函数等候指定的危险区段对象的所有权。当调用的线程被允许所有权时,函数返回。 
EnterCriticalSection (),一个单独进程的线程可以使用一个危险区段对象作为相互-排除同步。 进程负责分配被一个危险区段对象使用的内存, 它藉由声明一个CRITICAL_SECTION类型 的变量实现。在使用一个危险区段之前,进程的一些线程必须调用 InitializeCriticalSection 函数设定对象的初值.
为了要使互斥的访问被共享的资源,每个线程调用EnterCriticalSection 或者 TryEnterCriticalSection 功能,在执行访问被保护资源的任何代码段之前,请求危险区段的所有权。 


#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;
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);
 InitializeCriticalSection(&g_csA);
 InitializeCriticalSection(&g_csB);
 Sleep(40000);
 DeleteCriticalSection(&g_csA);
 DeleteCriticalSection(&g_csB);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  EnterCriticalSection(&g_csA);
  Sleep(1);
  //EnterCriticalSection(&g_csB);//临界区的同步和互锁
  if (tickets>0)
  {
   Sleep(1);
   cout<<"Thread1 sell ticket :"<<tickets--<<endl;
   //LeaveCriticalSection(&g_csB);
   LeaveCriticalSection(&g_csA);
  }
  else
  {
   //LeaveCriticalSection(&g_csB);
   LeaveCriticalSection(&g_csA);
   break;
  }
 }
 return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  EnterCriticalSection(&g_csB);
  Sleep(1);
  EnterCriticalSection(&g_csA);
  if (tickets>0)
  {
   Sleep(1);
   cout<<"Thread2 sell ticket :"<<tickets--<<endl;
   LeaveCriticalSection(&g_csA);
   LeaveCriticalSection(&g_csB);
  }
  else
  {
   LeaveCriticalSection(&g_csA);
   LeaveCriticalSection(&g_csB);
   break;
  }
 }
 return 0;
}

--------------------------------------------------------------------------------
 
二、使用互斥对象
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); 
如果时间是有信号状态返回WAIT_OBJECT_0,如果时间超过dwMilliseconds值但时间事件还是无信号状态则返回WAIT_TIMEOUT
WaitForSingleObject函数用来检测hHandle事件的信号状态,当函数的执行时间超过dwMilliseconds就返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回直才执行后面的代码。
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int index =0;
int tickets=100;
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,LPCTSTR("tickets"));
 //创建时主线程拥有该互斥对象,互斥对象的线程ID为主线程的ID,同时将该互斥对象内部计数器置为1
 if (hMutex)
 {
  if (ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"only one instance can run!"<<endl;
   //Sleep(40000);
   return;
  }
 }
 //**************************************************************
 WaitForSingleObject(hMutex,INFINITE);
 //使用该函数请求互斥对象时,虽说该对象处于无信号状态,但因为请求的线程ID和该互斥对象所有者的线程ID是相同的.所以仍然可以请求到这个互斥对象,于是该互斥对象内部计数器加1,内部计数器的值为2. 意思是有两个等待动作
 ReleaseMutex(hMutex);//释放一次互斥对象,该互斥对象内部计数器的值递减1,操作系统不会将这个互斥对象变为已通知状态.
 ReleaseMutex(hMutex);//释放一次互斥对象,该互斥对象内部计数器的值为0,同时将该对象设置为已通知状态.
 //对于互斥对象来说,谁拥有谁释放
 Sleep(40000);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  WaitForSingleObject(hMutex,INFINITE);//等待互斥对象有信号
  if (tickets>0)
  {
   Sleep(1);
   cout<<"thread1 sell ticket :"<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);//设置该互斥对象的线程ID为0,并且将该对象设置为有信号状态
 }
 return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  WaitForSingleObject(hMutex,INFINITE);
  if (tickets>0)
  {
   Sleep(1);
   cout<<"thread2 sell ticket :"<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);
 }
 return 0;
}

--------------------------------------------------------------------------------

三、使用事件对象
HANDLE     CreateEvent(   
        LPSECURITY_ATTRIBUTES     lpEventAttributes,           //     SD   
        BOOL     bManualReset,                                                 //     reset     type   
        BOOL     bInitialState,                                                     //     initial     state   
        LPCTSTR     lpName                                                      //     object     name   
    );   
    该函数创建一个Event同步对象,并返回该对象的Handle   
    
    lpEventAttributes     一般为NULL   
    bManualReset               创建的Event是自动复位还是人工复位     ,如果true,人工复位,   
    一旦该Event被设置为有信号,则它一直会等到ResetEvent()API被调用时才会恢复   
    为无信号.     如果为false,Event被设置为有信号,则当有一个wait到它的Thread时,   
    该Event就会自动复位,变成无信号.   
    bInitialState             初始状态,true,有信号,false无信号   
    lpName                           Event对象名   
    
    一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle()   
    来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号,用ResetEvent()   
    来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待   
    其变为有信号.   
    
    PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event   
    对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的.   
    对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于   
    人工复位的Event对象,它释放所有等待的thread.  
#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int tickets=100;
HANDLE g_hEvent;
void main()
{
 HANDLE hThread1;
 HANDLE hThread2;
 //**************************************************
 //创建一个命名的自动重置事件内核对象
 g_hEvent=CreateEvent(NULL,FALSE,FALSE,LPCTSTR("tickets"));
 if (g_hEvent)
 {
  if (ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"only one instance can run!"<<endl;
   return;
  }
 }
 //**************************************************
 SetEvent(g_hEvent);
 //创建线程
 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
 hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
 Sleep(40000);
 //关闭事件对象句柄
 CloseHandle(g_hEvent);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  WaitForSingleObject(g_hEvent,INFINITE);
  //ResetEvent(g_hEvent);
  if (tickets>0)
  {
   Sleep(1);
   cout<<"thread1 sell ticket :"<<tickets--<<endl;
   SetEvent(g_hEvent);
  }
  else
  {
   SetEvent(g_hEvent);
   break;
  }
 }
 return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
 while (TRUE)
 {
  WaitForSingleObject(g_hEvent,INFINITE);
  //ResetEvent(g_hEvent);
  if (tickets>0)
  {
   cout<<"Thread2 sell ticket :"<<tickets--<<endl;
   SetEvent(g_hEvent);
  }
  else
  {
   SetEvent(g_hEvent);
   break;
  }
 }
 return 0;















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值