《VC++深入详解》学习笔记 第十五章 多线程

1.基本概念

1)程序和进程:程序是计算机指令的集合,它以文件的形式存储在磁盘上,而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址控件中的一次执行活动(一个程序可以对应于多个线程)

2)进程组成:操作系统用来管理进程的内核对象(PCB)和地址空间

3)进程从来不执行任何东西,它只是线程的容器,若要使用进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码,也就是说:真正完成代码执行的是线程,进程只是线程的容器,或者说是线程的执行环境

4)单个进程可以包含一个(至少一个)或多个线程,当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程,主线程可以创建其他线程

5)系统赋予每个进程独立的虚拟地址空间

6)线程组成:线程的内核对象(TCB)和线程栈(用于维护线程在执行代码时需要的所有函数和局部变量,在所属进程的地址空间中分配内存)

7)线程可以访问进程的所有资源

8)线程运行:系统通过一种循环的方式为线程提供时间片(调度算法)

9)线程占的资源比较少,而且切换时不用交换地址空间,只改变执行环境,效率高

2.线程创建函数CreateThread

1)函数原型:HANDLE CreateThread(

           LPSECURITY_ATTRIBUTES  lpThreadAttributes,

           DWORD  dwStackSize,

           LPTHREAD_START_ROUTINE lpStartAddress.

           LPVOID  lpParameter,

           DWORD dwCreationFlags,

           LPDWORD lpThreadId

);

//NULL:默认安全性,如果希望它的子进程能继承该线程对象的句柄,则必须设定一个LPSECURITY_ATTRIBUTES结构体,将它的bInheritHandle成员初始化为TRUE

//设置线程初始栈的大小,如果值为0则默认使用与调用该函数的线程相同的栈空间大小

// lpStartAddress指向应用程序定义的LPTHREAD_START_ROUTINE类型的函数的指针,这个函数将由新线程执行,表明新线程的起始地址

   该函数名称任意,但函数类型必须遵照下述声明形式:

   DWORD WINAPI  ThreadProc(LPVOID lpParameter);

// lpParameter通过这个参数给新线程传递参数,该参数提供了一种将初始化值传递给线程函数的手段,这个参数的值既可以是一个数值,也可以是一个指向其他信息的指针

// dwCreationFlags设置用于控制线程创建的附加标记,它可以是CREATE_SUSPENDED0,若是前一,那么线程创建后处于暂停状态,知道程序调用了ResumeThread函数为止,若为0,那么线程在创建后立即运行

// lpThreadId这个参数是一个返回值,指向一个变量,用来接收线程ID,当创建一个线程时,系统会为该线程分配一个ID(如果不需要这个ID可以设为NULL

2)示例:

#include<windows.h>

#include<iostream>

using namespace std;

 

DWORD WINAPI Func1Proc(LPVOID lpParameter);

 

int main()

{  

HANDLE hThread;

          hThread = CreateThread(NULL,0,Func1Proc,NULL,0,NULL);

          CloseHandle(hThread);

          cout<<"main"<<endl;

          return 0;

}

 

DWORD WINAPI Func1Proc(LPVOID lpParameter)

{

          cout<<"Thread"<<endl;

          return 0;

}

该程序输出的结果为:mTahirne,maThiren等不确定的字符串(没有进行线程同步)

说明:包含必要的头文件:使用了Windows API函数,所以要包含windows.h文件

      创建完线程后,调用CloseHandle函数关闭新线程的句柄,实际上调用closeHandle函数并没有终止新创建的线程,只是表示在主线程中对新创建的线程的引用不感兴趣,因此将它关闭,另一方面,当关闭该句柄时,系统会递减该线程内核对象的使用计数,当创建的这个新线程执行完毕后,系统也会递减该线程内核对象的使用计数,当使用计数为0时,系统会释放该线程内核对象,如果没有关闭线程句柄,系统会一直保持这对线程内核对象的引用,这样该内核对象也就不会被释放,知道进程终止时,系统才会清理这些残留的对象

3.线程同步

1)利用互斥对象实现线程同步

     1.互斥对象属于内核对象,它能够确保线程拥有对单个资源的互斥访问权,互斥对象包含一个使用数量,一个线程ID和一个计数器,其中ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数

     2.创建互斥对象:CreateMutex,可以创建或打开一个命名的或匿名的互斥对象

       函数原型:HANDLE CreateMutex(

            LPSECURITY_ATTRIBUTES  lpMutexAttributes,//安全属性

                        BOOL  bInitialOwner,//指定互斥对象初始的拥有者,如果该值为真,则创建该互斥对象的线程获得该对象的所有权,否则,该线程将不获得其所有权

                      LPCTSTR  lpName//指定互斥对象的名称,若为NULL则创建一个匿名的互斥对象

);

    3.线程对共享资源访问结束后,应释放该对象的所有权:ReleaseMutex函数

      函数原型:BOOL ReleaseMutexHANDLE hMutex;

    4.线程必须主动请求共享对象使用权才能获得其所有权:WaitForSingleObject函数

     函数原型:DWORD WaitForSingleObjectHANDLE hHandle,DWORD dwMilliseconds;

// hHandle,所请求的对象的句柄,若互斥对象处于有信号状态就返回,否则一直等待

//指定等待的时间,若超过时间,函数也返回。若设置为INFINITE,则函数会永远等待

//成功返回WAIT_OBJECT_0  超时返回WAIT_TIMEOUT

    5.对互斥对象来说,谁拥有谁释放

    6.拥有互斥对象的线程用WaitForSingleObject再次请求该互斥对象,会使该互斥对象的内部计数器为21+1),要调用两次ReleaseMutex函数才能使内部计数器变为0,才能被其他线程使用,所以在使用互斥对象时要注意:如果多次在同一线程中请求同一互斥对象,那么需要相应地多次调用RealeaseMutex函数释放该互斥对象

2)保证应用程序只有一个实例运行

     可以通过命名的互斥对象来实现,在调用CreateMutex函数创建一个命名的互斥对象后,如果其返回值是一个有效的句柄,那么可以接着调用GetLastError函数,如果该函数返回的是ERROR_ALREADY_EXISTE,就表明先前已经创建了这个命名的互斥对象,因此就可以知道先前已经有该程序的一个实例在运行了

4.网络聊天室程序的实现(UDP

1)基于对话框—添加控件

2)加载套接字库:AfxSocketInit函数

    函数原型:BOOL AfxSocketInit(WSADATA* lpwsaData = NULL);//该函数内部调用WSAStartup函数来加载套接字库,且加载的是1.1版本的套接字库,该函数还可以保证应用程序在终止之前,调用WSACleanup函数终止对套接字的使用,并且利用该函数加载套接字时,不需要为工程链接ws2_32.lib库文件

              if (!AfxSocketInit())

          {

                   AfxMessageBox(IDP_SOCKETS_INIT_FAILED);

                   return FALSE;

          }

   TODO:应该在应用程序类重载的InitInstance函数中调用AfxSocketInit函数

          使用该函数,要包含相应的头文件afxsock.h,我们可以在stdafx.h中添加

#include <afxsock.h>            // MFC socket extensions

3)创建并初始化套接字

     1.CChatDlg类增加一个SOCKET类型的成员变量,m_socket(套接字描述符),并将其访问权限设为private,然后为CChatDlg类添加一个BOOL型的成员InitSocket,用来初始化该类的套接字成员变量

函数代码:BOOL CChatDlg::InitSocket()

{

   //创建套接字

     m_socket = socket(AF_INET,SOCK_DGRAM,0);

     if(INVALID_SOCKET == m_socket)

     {

              MessageBox("Fail To Create Socket!");

              return false;

     }

     //地址

     SOCKADDR_IN addrSock;

     addrSock.sin_family = AF_INET;

     addrSock.sin_port = htons(6000);

     addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

     //绑定套接字

     int retval = bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));

     if(SOCKET_ERROR == retval)

     {

              closesocket(m_socket);

              MessageBox("Fail To Bind Socket!");

              return false;

     }

     return true;

}

      2.CChatDlg类的OnInitDialog函数中调用InitSocket()函数

4)实现接收功能:因为在接收端接受数据时,如果没有数据到来,recvfrom函数会阻塞,从而导致程序暂停运行。所以,我们可以将接受数据的操作放置在一个单独的线程中完成,并给这个线程传递两个参数,一个是已经创建的套接字,一个是对话框控件的句柄,这样,在该线程中,当接收到数据后,可以将该数据传回给对话框(将两个参数封装在一个结构体,将地址赋给CreateThread函数的第四个参数)

       1.CChatDlg类的头文件中,该类的外部定义一个结构体

       struct RECVPARAM

{

 SOCKET sock;

 HWND hwnd;

};

        2.CChatDialog类的OnInitDialog函数中,创建线程,并传递所需的参数

    InitSocket();

    RECVPARAM *pRecvParam = new RECVPARAM;

        pRecvParam->sock = m_socket;

        pRecvParam->hwnd = m_hWnd;

       HANDLE hThread = CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);

        CloseHandle(hThread);

         3.线程函数声明为类的静态成员函数全局函数(不可以仅是类的成员函数)

static DWORD WINAPI RecvProc(LPVOID lpParameter);

         4.为线程函数添加代码

           DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)

{

         //获取主线程传递的套接字和窗口句柄

         SOCKET sock = ((RECVPARAM*)lpParameter)->sock;

         HWND hwnd = ((RECVPARAM*)lpParameter)->hwnd;

         delete lpParameter;

 

         SOCKADDR_IN addrFrom;

         int len = sizeof(SOCKADDR);

 

         char recvBuf[200];

         char tempBuf[300];

         int retval;

         while(true)

         {

                   //接受数据

                   retval = recvfrom(sock,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len);

                   if(SOCKET_ERROR == retval)

                            break;

                   sprintf(tempBuf,"%s:%s",inet_ntoa(addrFrom.sin_addr),recvBuf);

                   ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);//自定义消息,处理完后向对话框发送一条自定义消息,由对话框添加响应代码

         }

 

         return 0;

}

         5.CChatDlg类的头文件中,定义WM_RECVDATA这个消息的值,即:

           #define WM_RECVDATA  WM_USER+1

           CChatDlg类的头文件中,编写该消息响应函数原型的声明,即:

           afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);(对于自定义的消息,响应函数原型的声明要写在AFX_MSG宏外)

           CChatDlg类的源文件中,添加WM_RECVDATA消息映射,即:

           ON_MESSAGE(WM_PECVDATA,OnRecvData)(注意末尾不要有分号)

           CChatDlg类的源文件中,添加消息响应函数的实现

           void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)

{

         //取出接受到的数据

         CString str = (char*)lParam;

         CString strTemp;

         //获得已有数据

         GetDlgItemText(IDC_EDIT1,strTemp);

         str += "\r\n";

         str +=strTemp;

         //显示所有接收到的数据

         SetDlgItemText(IDC_EDIT1,str);

}

5)实现发送端功能

     1.添加发送按钮响应函数

     2.添加代码:

       void CChatDlg::OnButton1()

{

          //获取对方IP

          DWORD dwIP;

          ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);//获得IP框中数据

 

          SOCKADDR_IN addrTo;

          addrTo.sin_family = AF_INET;

          addrTo.sin_port = htons(6000);

          addrTo.sin_addr.S_un.S_addr = htonl(dwIP);

   

          //获取待发送数据

          CString strSend;

          GetDlgItemText(IDC_EDIT2,strSend);

          //发送数据

sendto(m_socket,strSend,strSend.GetLength()+1,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR));

          //清空发送编辑框中的内容

          SetDlgItemText(IDC_EDIT2,"");

 

}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值