孙鑫MFC笔记(15)--多线程和聊天室的创建

多线程聊天室的实现 C++ 使用MFC进行网络编程,实现多线程聊天室 1.MFC网络编程要包含库文件 #include ,可以在stdafx.h中包含此文件 2.MFC套接字的初始化可以用AfxSocketInit()这个函数来实现。这个函数可以写在APP类中的initInstance这个函数中。 以下是主要代码实现部分 1.首先编写初始化套接字的函数 ////////////////////////////// 阅读详情

孙鑫MFC笔记(15)--多线程和聊天室的创建

 

代码1分析:

说明:对于单核cpu的电脑来说,线程都在自己的时间片中运行,单位时间内,系统只能运行一个线程,交替运行;对于多核cpu或多cpu的电脑来说,才是真正意义上的单位时间内运行多个线程

 

说明:如果采用向导创建的Win32 Console程序选择的是空项目时,则以下使用方式是正确的,如果选择是”hello world”的简单应用程序时,则在main函数中想要使用输入输出流,则必须使用:

#include <iostream>

using namespace std;

 

#include <windows.h> //调用API函数

#include <iostream.h>

 

说明:程序有一个主线程,入口函数main,在主线程上可以创建新线程,入口函数定义成如下形式:

DWORD WINAPI ThreadProc1(LPVOID lpParameter); //函数声明

 

void main()

{

      HANDLE handle=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL); //创建新线程

 

      说明:调用CloseHandle函数并没有终止新创建的线程,只是表明在主线程中对新创建的线程的引用不是很感兴趣而已,所以在没有在做什么动作之前就关闭了,这个函数在主线程运行过程中必须调用,否则即使对应线程运行结束,对应句柄也是不会关闭的,知道主线程退出时,才会由系统集中清理。

      CloseHandle(handle);

      cout<<"main thread is running"<<endl;

 

      说明:如果不添加Sleep语句,主线程会在自己的时间片中运行完成后(该时间片在main函数,也就是主线程全部执行完毕后还有时间剩余),选择直接退出,主线程都退出了,依附于主线程的新线程也就不会有机会得到执行了,只有让主线程暂停执行(采用sleep函数),即挂起,让出执行的权利,操作系统会从等待的线程中选择一个来运行,那么新创建的线程得到机会执行

      Sleep(10);

}

 

DWORD WINAPI ThreadProc1(LPVOID lpParameter)

{

      cout<<"thread1 is running"<<endl;

      return 0;

}

 

输出结果:

main thread is running

thread1 is running

 

代码2分析:

添加全局变量

int index=0;

将main函数中输出语句修改为:

while(index++<50)

      cout<<"main thread is running"<<endl;

将线程中输出语句修改为:

while(index++<50)

      cout<<"thread1 is running!"<<endl;

将main函数中sleep语句省去

 

则得到的结果是:

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

thread1 is running!

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

main thread is running

 

结果说明主线程和副线程在交替运行,也就是主线程在它的时间片运行结束后,副线程得到执行的权利,在它自己所对应的时间片中运行,此时主线程其实还没有运行结束,它将等待着副线程运行结束后继续执行

 

代码3分析(车票的销售):

#include <windows.h>

#include <iostream.h>

 

DWORD WINAPI ThreadProc1(LPVOID lpParameter);

DWORD WINAPI ThreadProc2(LPVOID lpParameter);

 

int ticket=50;

 

void main()

{

      HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);

      HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

      CloseHandle(handle1);

      CloseHandle(handle2);

 

      说明:为了使得主线程在退出之前保证副进程的执行完成,有些实现方法是采用恒真的空循环,单此种方法主线程会占用cpu的运行时间,如果采用Sleep,则主线程完全不占用cpu的任何运行时间

Sleep(4000);

}

 

DWORD WINAPI ThreadProc1(LPVOID lpParameter)

{

      说明:在线程的时间片内持续运行

      while(TRUE)

      {

           if(ticket>0)

                 cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;

           else

                 break;

      }

      return 0;

}

 

DWORD WINAPI ThreadProc2(LPVOID lpParameter)

{

      while(TRUE)

      {

           if(ticket>0)

                 cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;

           else

                 break;

      }

      return 0;

}

 

输出结果:

thread1 sale the ticket id is:50

thread1 sale the ticket id is:49

thread1 sale the ticket id is:48

thread1 sale the ticket id is:47

thread1 sale the ticket id is:46

thread1 sale the ticket id is:45

thread1 sale the ticket id is:44

thread1 sale the ticket id is:43

thread1 sale the ticket id is:42

thread1 sale the ticket id is:41

thread1 sale the ticket id is:40

thread1 sale the ticket id is:39

thread1 sale the ticket id is:38

thread1 sale the ticket id is:37

thread1 sale the ticket id is:36

thread1 sale the ticket id is:35

thread1 sale the ticket id is:35

thread2 sale the ticket id is:34

thread2 sale the ticket id is:33

thread2 sale the ticket id is:32

thread2 sale the ticket id is:31

thread2 sale the ticket id is:30

thread2 sale the ticket id is:29

thread2 sale the ticket id is:28

thread2 sale the ticket id is:27

thread2 sale the ticket id is:26

thread2 sale the ticket id is:25

thread2 sale the ticket id is:24

thread2 sale the ticket id is:23

thread2 sale the ticket id is:22

thread2 sale the ticket id is:21

thread2 sale the ticket id is:20

thread2 sale the ticket id is:19

thread1 sale the ticket id is:18

thread1 sale the ticket id is:17

thread1 sale the ticket id is:16

thread1 sale the ticket id is:15

thread1 sale the ticket id is:14

thread1 sale the ticket id is:13

thread1 sale the ticket id is:12

thread1 sale the ticket id is:11

thread1 sale the ticket id is:10

thread1 sale the ticket id is:9

thread1 sale the ticket id is:8

thread1 sale the ticket id is:7

thread1 sale the ticket id is:6

thread1 sale the ticket id is:5

thread1 sale the ticket id is:4

thread1 sale the ticket id is:3

thread1 sale the ticket id is:2

thread1 sale the ticket id is:1

 

我们发现结果一切正常,但是有可能会遇到如下一种情况:

当ticket数量运行到1时,线程1正在运行,此时线程1运行到输出语句时,它的时间片已经结束,则线程1对ticket id的减减动作没有完成,此时线程2开始执行,发现数量是1,则执行减减动作,使得数量为0,返回,线程1继续执行,此时票的数量已经是0了,线程1继续执行输出语句,对票的数量执行减减,则数量变为-1,这是不允许的。这是由于抢占全局的资源所引起的。

解决这个问题的办法是实现线程间的“同步”,即一个线程在对一个全局的资源进行操作的过程中,是不允许其他线程对全局的资源进行访问,直到该线程对资源操作完毕后。

涉及概念:互斥对象

n       互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。

n       互斥对象包含一个使用数量,一个线程ID和一个计数器。

n       ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

涉及到三个函数:

1.              CreateMutex:创建互斥对象,返回互斥对象的句柄,其中第二个参数如果是TRUE,表示调用该函数的线程拥有互斥对象的所有权,即互斥对象处于非空闲状态。如果是FALSE,则表示当前互斥对象处于空闲状态,其他线程可以占用。

2.              WaitForSingleObject:等待互斥对象的使用权,如果第二个参数设置为INFINITE,则表示会持续等待下去,直到拥有所有权,才有权执行该函数下面的语句。一旦拥有了所有权,则会将互斥对象的的线程ID设置为当前使用的线程ID值。

3.              ReleaseMutex:将互斥对象所有权进行释放,交还给系统。

可以将互斥对象想象成一把钥匙,CreateMutex创建了这把钥匙,WaitForSingleObject等待这把钥匙去访问一个公共的资源,比如一个房间,如果拥有了钥匙,则这个房间的所有权就属于这个进程了,别人是进不去这个房间的,直到进程将这个房间的钥匙归还掉,即ReleaseMutex。

 

具体实现代码:

#include <windows.h>

#include <iostream.h>

 

DWORD WINAPI ThreadProc1(LPVOID lpParameter);

DWORD WINAPI ThreadProc2(LPVOID lpParameter);

 

int ticket=100;

HANDLE hMutex;

 

void main()

{

      HANDLE handle1=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);

      HANDLE handle2=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

      CloseHandle(handle1);

      CloseHandle(handle2);

 

      hMutex=CreateMutex(NULL,FALSE,NULL); //第二个参数为FALSE,将互斥对象声明为空闲状态

 

      Sleep(4000);

}

 

DWORD WINAPI ThreadProc1(LPVOID lpParameter)

{

      while(TRUE)

      {

           WaitForSingleObject(hMutex,INFINITE); //第二个参数为INFINITE表示一直等待,直到拥有互斥对象

           if(ticket>0)

           {

                 Sleep(1);

                 cout<<"thread1 sale the ticket id is:"<<ticket--<<endl;

           }

           else

                 break;

           ReleaseMutex(hMutex); //使用完了,将互斥对象还给操作系统

      }

      return 0;

}

 

DWORD WINAPI ThreadProc2(LPVOID lpParameter)

{

      while(TRUE)

      {

           WaitForSingleObject(hMutex,INFINITE);

           if(ticket>0)

           {

                 Sleep(1);

                 cout<<"thread2 sale the ticket id is:"<<ticket--<<endl;

           }

           else

                 break;

           ReleaseMutex(hMutex);

      }

      return 0;

}

 

思考:如果将WaitForSingleObject和ReleaseMutex放置在while循环的外部,发现程序的输出结果是只有线程1在销售票,线程2没有销售一张票,这是因为线程1在得到互斥对象的所有权后,进入到了循环,而释放互斥权的调用必须等到循环执行结束,即使线程1在时间片完成后,将执行权交给了线程2,单线程2发现互斥对象的所有权还是被占用着,所以没有做任何动作,线程1继续执行,直到将票销售完,退出循环,释放互斥对象的所有权,此时线程2在得到所有权后发现票已经销售一空,也就退出了。在上述代码中,为什么输出结果正确呢,也就是线程1和2交替售票,这是因为线程1得到互斥对象的控制权后执行单张票的销售动作,动作完成就立即释放了控制权。在线程1的执行时间片完成后,线程2就开始执行了,线程2执行和线程1相同的动作。

      两段代码最主要的区别就是前者在销售所有票的过程中都独占着互斥对象资源,而后者是销售完一张票后就将互斥对象资源释放掉了。

 

几点思考:

1.      在main函数中将CreateMutex 的第二个参数修改为TRUE,则表示主线程在创建互斥对象的时候就拥有了所有权,执行代码,发现线程1和2都没有执行,只是因为主线程没有释放掉互斥对象。考虑在线程1和2的WaitForSingleObject前添加ReleaseMutex可否?也就是我在申请前将别人占用的互斥对象释放掉,这显然是不行的,不然就失去互斥的真正意义了,如果我自己想用,就将别人踢掉,就失去了规则了。那内部是怎么实现的呢?在CreateMutex或WaitForSingleObject申请到互斥对象的所有权后,会将互斥对象中的线程ID设置为调用线程的ID,ReleaseMutex时会比较当前的线程ID和互斥对象中的ID是否一致,如果不一致,则禁止释放。所以要想线程1和2能正常执行,则必须在CreateMutex后调用ReleaseMutex。

2.      在main函数中CreateMutex的第二个参数修改为TRUE后,互斥对象的所有权由主线程所有,然后调用WaitForSingleObject,发现申请依然成功,这是因为拥有所有权的线程是自身线程,单互斥对象的内部发生了变化,它内部的计数器设置成了2,也就是如果希望线程1和2能执行,则需要两次调用ReleaseMutex。ReleaseMutex函数的意义就是将计数器递减。

3.      如果在线程中调用WaitForSingleObject后没有调用ReleaseMutex,则该线程执行终止后(不是单次时间片完成后),操作系统会自动释放掉互斥对象

 

让程序单位时间内只能运行一个实例,也就是如果实例存在,则不打开新的实例:

由CreateMutex在MSDN中的介绍可以知道,只需要将该函数的第三个参数,Mutex对象的名字不设置成NULL,即给它取个名字,然后代码修改如下:

hMutex=CreateMutex(NULL,FALSE,”instance”); //Mutex的名称可以任意的取

      if(hMutex)

      {

           if(ERROR_ALREADY_EXISTS==GetLastError())

           {

                 cout<<"the application instance is exit!"<<endl;

                 return;

           }

      }

 

创建基于多线程的聊天室程序:

1.      创建一个基于对话框的MFC程序,界面如下:


 

2.      添加套接字库头文件:

调用MFC的内置函数:AfxSocketInit,该函数其实也是调用Win32中的WSAStartup(见上一章的介绍),并且是调用1.1的套接字库版本,该函数能确保程序终止前调用WSACleanup的调用,该函数的放置位置最好在CWinApp中的InitInstance中,注意包含头文件Afxsock.h,在StdAfx.h这个头文件中进行包含。StdAfx.h头文件是一个预编译头文件,在该文件中包含了MFC程序运行的一些必要的头文件,如afxwin.h这样的MFC核心头文件等。它是第一个被程序加载的文件。

 

3.      加载套接字库:

在CWinApp中的InitInstance添加如下代码:

if(FALSE==AfxSocketInit())

      {

           AfxMessageBox("套接字库加载失败!");

           return FALSE;

}

 

4.      创建套接字,将自己假想成服务器端,进行套接字和地址结构的绑定,等待别人发送消息过来。

在CDialog中

添加成员变量:SOCKET m_socket

添加自定义函数:

BOOL CChatDlg::InitSocket()

{

      m_socket=socket(AF_INET,SOCK_DGRAM,0); //UDP连接方式

      if(INVALID_SOCKET==m_socket)

      {

           MessageBox("套接字创建失败!");

           return FALSE;

      }

 

      SOCKADDR_IN addrServer; //将自己假想成server

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

      addrServer.sin_family=AF_INET;

      addrServer.sin_port=htons(1234);

     

      int retVal;

      retVal=bind(addrSock,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));

      if(SOCKET_ERROR==retVal)

      {

           closesocket(addrSock);

           MessageBox("套接字绑定失败!");

           return FALSE;

      }

      return TRUE;

}

 

5.      在CChatDlg类的外部添加结构体:

struct RECVPARAM

{

说明:因为套接字本身只涉及传输的协议类型,是UDP还是TCP,而和是服务器端还是客户端没有必然的关系,所以本程序即涉及到服务器端又涉及到客户端,采用同一个套接字是被允许的。

            SOCKET sock; //保存最初创建的套接字

            HWND hWnd; //保存对话框的窗口句柄

};

 

6.      在对话框的初始化代码中完成线程的创建:

在CChatDlg::OnInitDialog函数中添加下面的代码:

if(!InitSocket()) //服务器端的创建

           return FALSE;

 

      RECVPARAM *pRecvParam=new RECVPARAM;

      pRecvParam->hWnd=m_hWnd;

      pRecvParam->sock=m_socket;

 

说明:

1.      接收部分应该一直处于响应状态,如果和发送部分放在同一段代码中,势必会阻塞掉发送功能的实现,所以考虑将接收放在单独的线程中,使它在一个while循环中,始终处于响应状态

2.      因为需要传递两个参数进去,一个是recvfrom需要用的套接字,另一个是当收到数据后需要将数据显示在窗口中的对应文本框控件上,所以需要传递当前窗口的句柄,但CreateThread方法只能传递一个参数,即第四个参数,这时候就想到了采用结构体的方式传递。

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

CloseHandle(hThread);

 

7.      创建线程入口函数RecvProc:

可模仿ThreadProc的创建方式(在MSDN中有原型),但遇到一个问题,将该函数申明为CChatDlg的成员函数嘛?答案不是的,因为如果是成员函数的话,那它属于某个具体的对象,那么在调用它的时候势必要让程序创建一个对象,但该对象的构造函数有参数的话,系统就不知所措了,所以可以将函数创建为全局函数,即不属于类,但这失去了类的封装性,最好的方法是将该方法声明为静态方法,它不属于任何一个对象。

在CChatDlg类的头文件中添加:

static DWORD WINAPI RecvProc(LPVOID lpParameter);

 

在cpp文件中添加:

DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)

{

      RECVPARAM* pRecvParam=(RECVPARAM*)lpParameter;

      HWND hWnd=pRecvParam->hWnd;

      SOCKET sock=pRecvParam->sock;

      char recvBuf[200];

      char resultBuf[200];

      SOCKADDR_IN addrFrom; //这个时候是假想成服务器端

      int len=sizeof(SOCKADDR_IN);

      while(TRUE) //处于持续响应状态

      {

           int retVal=recvfrom(sock,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len); //从客户端接收数据,并将客户端的地址结构体填充

           if(SOCKET_ERROR == retVal)

           {

                 AfxMessageBox("接收数据出错"); //因为本函数是静态函数,所以只能调用全局的消息了

                 break;

           }

           else

           {

sprintf(resultBuf,"%s said:%s",inet_ntoa(addFrom.sin_addr),recvBuf);

//现在已经拿到客户端送过来的消息了,但因为自身是静态函数,所以拿不到当前窗口对象中的控件的句柄,也就不能对其赋值了,唯一办法就是用消息的形式将接收到的值抛出到窗口的消息队列中,等待消息处理

                 ::PostMessage(hWnd,WM_RECVDATA,0,(LPARAM)resultBuf);                }

      }

      return 0;

}

8.      自定义消息:

定义自定义消息的宏:

#define WM_RECVDATA WM_USER+1

声明消息响应函数:因为有参数要传递,所以wParam和lParam都要写,如果没有参数需要传递,可以不写

afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);

消息映射:

ON_MESSAGE(WM_RECVDATA,OnRecvData)

定义消息响应函数:

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

{

           //注意将文本框的属性设置成多行

      CString recvData=(char*)lParam;

      CString temp; //文本框中现有的内容

      GetDlgItemText(IDC_EDIT_RECV,temp);

      temp+="/r/n";

      temp+=recvData;

      SetDlgItemText(IDC_EDIT_RECV,temp);

}

自此,消息的接收和显示部分已经完成了

 

9.      消息的发送:

在发送按钮点击的响应函数中添加:

       DWORD dword;

      CIPAddressCtrl* pIPAddr=(CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1);

      pIPAddr->GetAddress(dword);

 

      //因为对方有具体的IP地址值,我们假想对方是服务器端。在发送的时候程序就从服务器的角色转变为客户端了

      SOCKADDR_IN addrServer;

      addrServer.sin_addr.S_un.S_addr=htonl(dword);

      addrServer.sin_family=AF_INET;

      addrServer.sin_port=htons(1234);

 

      CString strSend;

      GetDlgItemText(IDC_EDIT_SEND,strSend);

      sendto(m_socket,strSend,strlen(strSend)+1,0,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));

 

SetDlgItemText(IDC_EDIT_SEND,"");

 

 

几点思考:

1.      本程序的核心在于将消息的发送的和接收发在了两个不同的线程中,接收放在新创建的副进程中,因为其要一直处于响应状态,也就是需要一个while循环;发送放在主线程中。这样消息的接收和发送就不存在先后顺序了,且一直处于循环中的接收也不会影响到发送。

2.      上述代码中的新线程入口函数中可能没有必要传递两个参数进去,其中SOCKET参数可以在入口函数内部创建,反正SOCKET变量也就是声明是TCP还是UDP,和发送或接收没有必然的联系,如果这样的话,就没有必要声明第五步中的结构体了,CreateThread方法也刚好传递一个参数,即当前窗口的句柄

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/roger_ge/archive/2008/09/08/2896576.aspx

其他参考:

http://mxmkeep.blog.163.com/blog/static/106492415200992414047520/
http://blog.chinaunix.net/u1/50617/showart.php?id=481327
http://blog.sina.com.cn/s/blog_57f53ed201000ceh.html
http://blog.youkuaiyun.com/roger_ge/archive/2008/09/08/2896576.aspx

======================================================================================

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
  DWORD dwStackSize,                        // initial stack size 页面是系统在管理内存时使用的一个内存单位,不同的CPU其页面大小也是不同的,为0则和调用线程一样的大小
  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function 表示线程的起始地址,对于主线程来说main函数为它入口的入口函数,对于我们创造的线程来说也需要一个入口函数,这个函数的地址就在第三个函数这里指定,名字是自己取的
  LPVOID lpParameter,                       // thread argument
  DWORD dwCreationFlags,                    // creation option 如果是0创建完成后立即运行,若不立即运行具见MSDN
  LPDWORD lpThreadId                        // thread identifier
);

多线程:

#include <Windows.h>
#include <iostream>
using namespace std;

DWORD WINAPI Fun1Proc1( LPVOID lpParameter );
DWORD WINAPI Fun1Proc2( LPVOID lpParameter );

int tickets = 100;
HANDLE hMutex;

int main()
{
 HANDLE hThread1,hThread2;
 hThread1 = CreateThread(NULL,0,Fun1Proc1,NULL,0,NULL);//创建一个线程
 hThread2 = CreateThread(NULL,0,Fun1Proc2,NULL,0,NULL);
 CloseHandle(hThread1);//关闭线程句柄,但线程并没有被关闭
 CloseHandle(hThread1);

 /*
 while (index++ < 1000)
 {
 cout<<"main thread is running"<<endl;
 }
 */

 //创建一个或者打开一个命名的或者没有命名的互斥对象
 //hMutex = CreateMutex(NULL,FALSE,NULL);//第二个参数为false,当前就没有线程拥有这个互斥对象,操作系统就会将这个
 //互斥对象设为已通知状态,也就是有信号状态,若为true则是主线程拥有了这个互斥对象

 //第一次创建这个互斥对象的时候,主线程拥有了这个互斥对象,除了将这个互斥对象的线程ID设为主线程的线程ID
 //同时将我们的互斥对象的计数器设为1,互斥对象为未通知状态,WaitForSingleObject又请求到,所以计数器2,
 //我们调用ReleaseMutex(hMutex);实际上是计数器减一,2次计数器为0,操作系统将互斥对象的线程ID设为0,
 //互斥对象设为已通知状态

 //hMutex = CreateMutex(NULL,TRUE,NULL);
 hMutex = CreateMutex(NULL,TRUE,"tickets");
 if(hMutex)
 {

  if(ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"the application instance is exit!"<<endl;
   return 0;
  }
 }

 WaitForSingleObject(hMutex,INFINITE);//1.互斥对象ID和线程ID一样也可以调用 2.互斥对象设为已通知状态,也就是有信号状态
 ReleaseMutex(hMutex);
 ReleaseMutex(hMutex);

 Sleep(4000);//指定暂停时间间隔,放弃运行,系统调用子线程

 return 0;
}

DWORD WINAPI Fun1Proc1( LPVOID lpParameter )
{
 while(TRUE)
 {
  WaitForSingleObject(hMutex,INFINITE);//其返回值是可以判断进程是异常终止还是正常获得1:14:32
  if(tickets > 0)
  {
   Sleep(1);
   cout<<"thread1 sell tickets:"<<tickets--<<endl;
  }
  else
  {
   break;
  }
  ReleaseMutex(hMutex);
 }

 return 0;
}

DWORD WINAPI Fun1Proc2( LPVOID lpParameter )
{
 while(TRUE)
 {
  WaitForSingleObject(hMutex,INFINITE);
  if(tickets > 0)
  {
   Sleep(1);
   cout<<"thread2 sell tickets:"<<tickets--<<endl;
  }
  else
  {
   break;
  }
  ReleaseMutex(hMutex);
 }

 return 0;
}

 

我的大学--孙鑫MFC笔记教程 孙鑫MFC笔记教程(1)--Windows程序的内部运行原理   源文档 http://blog.youkuaiyun.com/liufei_learning/archive/2010/09/02/5859925.aspx> 孙鑫MFC笔记教程(2)--C++经典语法与应用 阅读详情

相关推荐

MFC IOCP模型异步IO

新手学习iocp的好例子~MFC下的完成端口IOCP源码学习线程池与完成端口的朋友可参考参考

MFC:异步套接字编程举例—网络聊天室程序

网络聊天室程序(MFC:利用win32的扩展函数): int WSAStartup(   WORD wVersionRequested,   LPWSADATA lpWSAData ); 一、加载套接字库: //本程序要用到winsock2版本的函数,所以还要自己调用WSAStartup函数。将帮助文档中的例子代码复制 //放到CChatApp的InitInstance()函数中,并

yangchuankai's blog 3031

MFC多线程编程实例

这是一个使用VC++编写的一个MFC多线程的源程序,程序中线程执行函数有两种,一种是从CWinThread继承的对象的RUNTIME_CLASS,用于线程启动对话框,另一种是类的成员函数作为线程的执行函数。另外本程序还修改了MFC生成的.exe程序的图标和运行时对话框显示的图标。还有动态改变对话框图标的功能。希望该程序对大家有帮助。

网络聊天室(MFC编程)

本应用是一款简单的模拟qq聊天应用.主要分为服务器端与客户端服务器select端:客户端client:服务器端代码如下:select.cpp:// select.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include "dataHandle.h"#include#pragma comment(lib, "ws2_32")void SockInit();i...

qq_29762941的博客 1万+

MFC多线程完整总结及示例代码

介绍了多线程创建、停止的完整过程,并附加了完整代码及注意事项避免内存泄露等问题发生

ASReview:基于主动学习的AI文献筛选工具,提升科研效率

主动学习是机器学习的一个重要分支,其核心原理是让算法能够主动选择最有价值的数据进行标注,从而以更低的成本获得更好的模型性能。这一技术通过人机协作的闭环流程,显著提升了数据标注和模型训练的效率和精度。在工程实践中,主动学习尤其适用于数据标注成本高昂或数据类别不平衡的场景,例如医学影像分析、自然语言处理中的文本分类以及推荐系统的冷启动问题。通过优先标注模型最不确定的样本,它能够有效解决传统机器学习面临的冷启动和数据稀疏性挑战。本文聚焦的ASReview项目,正是将主动学习原理应用于系统文献综述这一具体场景,通过

weixin_28725959的博客 431

MFC异步Socket从零开始实现

在pch.h中引入socket头文件 #define _WINSOCK_DEPRECATED_NO_WARNINGS #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") 2. 在xxDlg.h中定义socket消息 #define WM_SOCKET WM_USER + 1000 在类向导中添加消息,使用快捷键Ctrl+Shift+X打开类向导 在xxDlg.h中定义相关对象 SOCKET sSocket, cSocket.

Scarlett的博客 1051

通过Scrapy爬虫抓取Deep Learning领域的论文数据

然而,大部分高质量的deep learning研究论文都是通过国际顶尖学术会议(如neurips、iclr、icml等)发表的,而这些论文的获取难度较大。在本文中,我们选择抓取arXiv的数据。arXiv是一个管理科学论文的网站,其中包括了许多领域的论文,也包括了Deep Learning领域的论文。同时,arXiv网站还提供了方便的API接口,使得我们的爬虫程序可以很方便地获取论文数据。通过结合其他的数据处理技术,我们可以对这些数据进行更深入的研究和分析,从而推动Deep Learning领域的发展。

Ob2024的博客 609

孙鑫VC视频教程笔记之第十五课“多线程与聊天室”

  代码1分析:说明:对于单核cpu的电脑来说,线程都在自己的时间片中运行,单位时间内,系统只能运行一个线程,交替运行;对于多核cpu或多cpu的电脑来说,才是真正意义上的单位时间内运行多个线程 说明:如果采用向导创建的Win32 Console程序选择的是空项目时,则以下使用方式是正确的,如果选择是”hello world”的简单应用程序时,则在main函数中想要使用输入输出流,

Roger Ge 的专栏 5886

IFIX往Mysql数据库同步数据方法

通过IFix数据库向Mysql数据库同步数据

仙人掌 1596

思博伦Spirent TestCenter创建单播流uni-stream

Traffic Generator列表中会出现刚才创建的uni-stream,创建完成。通常测试情况下,主要对Ethenet II和IPv4 Header进行配置。(通常情况下,如果流配置比较规范,选择任意端口接收,也没问题。编辑uni-stream基本信息。配置本条流由STC哪个端口接收。

辗转反测的博客 1681

孙鑫VC++视频笔记(15)多线程与聊天室的创建

程序、进城和线程——程序和进程程序是计算机指令的集合,它以文件的形式存储在磁盘上。进程:通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立的运行的单位,因此,他不占用系统的运行资源。进程由两个部分组成:1、操作系统用来管理进程的内核对象。内

Welcom to zougangx's blog 890

Claude Code教程(二) | Claude Code 核心信息与使用指南

Claude Code 核心信息与使用指南

qq_20236937的博客 2185

孙鑫MFC笔记之十四--多线程同步与异步套接字编程

多线程同步编程方法1 事件对象 n事件对象也属于内核对象,包含一个使用计数,一个用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。 n有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的

工作笔记 8305

当vCenter的证书过期、Root密码过期、Root密码遗忘同时发生时的解决方法与步骤

vCenter,强制修改root密码,修改SSO User密码,修复证书过期问题

OCTSJimmy的专栏 5547

Flow Matching03-01:微分方程【微分方程(描述变化规律的方程):包含未知函数和它的导数的方程】【公式解(精确)、数值解(逼近)】

好的,我将从数学角度详细解释Flow Matching,从高中生能理解的基础知识开始,逐步深入到高级概念。

u013250861的博客 219

MFC 孙鑫 15.6网络聊天室创建 recvfrom返回1054

在学习孙鑫老师的VC++深入解析时,15.6章多线程的学习中例子网络聊天室创建一直有问题。系统:win7 sp1 IDE:VC2013首先打断点查看,发现recvfrom返回-1;加入代码查看具体错误while (TRUE) { retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*) &amp; addrFrom, &amp;len); ...

Andrew的博客 566

996引擎 - 读写变量性能测试

单轮100万次读写,取3轮平均耗时(毫秒),有效读取率均为100%,数值越小性能越优。

灵台方寸山斜月三星洞 244

C++聊天室(孙鑫MFC总结)

老师的作业,让做一个局域网聊天室!看了孙鑫的视频!自己也简单的实现了一下!感觉挺棒的! 一个中心-----"练习" 下面贴出自己的简单理解,告诉自动掠过,,,,,,,,, 聊天室基于UDP协议, 1.套接字初始化 AfxSocketInit()函数初始化 1 if(!AfxSocketInit())2 {3 AfxMessageBox("加载套...

weixin_34248023的博客 538

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

孙鑫 MFC 学习 聊天室程序

以下是在vs2008上写的孙鑫老师第十五讲中的聊天室程序,里面花时间最多的是CString、string和char*之间的转换。 由于初学,目前的解决办法中将 字符集 进行了设置,不知道这样对程序的健壮性或者可移植性有没有影响,不管怎样,实现出来了。下面是设置字符集的截图。 下面是ChatDlg.h 文件 ChatDlg.h 1 // ChatDlg.h : 头文件 ...

weixin_30287169的博客 287

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

MFC笔记之多线程聊天室

新手刚接触,跟着孙鑫老师视频一步一步的做。从VC6.0到VS2010好像并不是那么顺利,下面记录下一点收获。 网络编程的一般步骤: 1声明套接字版本(WSAStartup);2创建套接字(socket);3绑定套接字(bind);4发送接收(sendto/recvfrom);5关闭(closesocket) 第1~3步代码如下: 1 CString error; 2 WORD w...

weixin_33816946的博客 214

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Lesson15多线程与聊天室程序的创建 VS2013 VC++深入详解 孙鑫

1. 新建项目Chat 2. UI操作 3. 代码 3.1 初始化socket 3.2 ChatDlg.h : header file 3.3 ChatDlg.cpp 4. 程序执行画面 1. 新建项目Chat MFC Application-&gt;Dialog Based(基于对话框的)exe项目 2. UI操作 将自带的文本控件与按钮控件删除。 将发送按钮(IDC_BT...

独步天秤的博客 882
上一篇: 孙鑫MFC(14)--网络编程
下一篇: 孙鑫MFC笔记教程(16)--线程同步和异步套接字编程
liufei_learning
博客等级 码龄17年 821粉丝 145原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值