第15章 多线程
转自: https://blog.csdn.net/u014162133/article/details/46573873
1、程序,进程,线程
A: 程序是计算机指令的集合,它以文件的形式存储在磁盘上,而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动.一个程序可以对应多个进程.
进程是资源申请,调度和独立运行的单位,因此,它使用系统中的运行资源,而程序不能申请系统资源,不能被系统调度也不能作为独立运行的单位,因此它不占系统运行资源.
进程组成:
<1> 操作系统用来管理进行的内核对象
内核对象也是系统用来存放关于进程的统计信息的地方.内核对象是操作系统内部分配的一个内在块,该内存块是一种数据结构,其成员负责维护该对象的各种信息.
<2> 地址空间
它包含所有可执行模块或DLL模块的代码和数据.另外,它也包含动态内存分配的空间,例如线程的栈和堆分配空间
B: 进程从来不执行任何东西,它只是线程的容器,若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间的中的代码.也就是,真正完成代码执行的是线程,而进程只是线程的容器,或者说是线程的执行环境.
单个进程可能包含若干个线程,这些线程都”同时”执行进行地址空间的中代码,每个进程至少拥有一个线程,来执行进程的地址空间中的代码,当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程.也就是执行main函数或WinMain函数的线程,可以把main函数或WinMain函数看作是主线程的入口点函数.此后,主线程可以创建其它的线程.
C: 线程也由两部分组成:
<1> 线程内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地地方
<2>线程栈stack:它用于维护线程在执行代码时需要的所有函数参数和局部变量.线程总是在某个进程环境中创建,系统从进程的地址空间中分配内存,供线程的栈使用.
线程运行:
操作系统为每一个运行线程安排一定的CPU时间—时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就是好像多个线程是同时运行一样.
2、创建线程的SDK函数
HANDLE CreateThread (
SEC_ATTRS SecurityAttributes,//安全结构全指针,NULL为默认安全性
ULONG StackSize,//线程初始栈的大小 ,以字节为单位,如果为0或小于0,默认将使用
//与调用该线程相同的栈空间大小
SEC_THREAD_START StartFunction,//线程处理函数地址(可用函数名)
PVOID ThreadParameter,//可以为数据或其它信息的指针,表示给新线程传送参数
ULONG CreationFlags,//线程创建的标记,可以为0或CREATE_SUSPENDED,如果
//CREATE_SUSPENDED线程创建后暂停状态,直到程序调用//ResumeThread,如果为0立即运行
PULONG ThreadId //[out]返回值,系统分配新的线程ID
);
3. 一个简单的多线程程序,模拟售票系统
#include <windows.h>//必要的头文件,使用Windows API函数 #include <iostream.h> int index = 0; int tickets = 100;//票数 HANDLE hMutex; //使用全局的互斥对象来保证对同一资源的互斥访问与操作这里是tickets //线程处理函数原型,形式可从MSDN中拷贝 //线程1 的入口函数 DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ); DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data ); void main(){ HANDLE hThread1; DWORD thread1ID; //创建线程1 hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, &thread1ID); HANDLE hThread2; DWORD thread2ID; //创建线程2 hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, &thread2ID); CloseHandle(hThread1); //关闭线程的句柄,为什么要关闭?它将线程的使用计数减1 CloseHandle(hThread2);//这样当线程结束时,线程内核对象被释放, //否则只有当进程结束,才释放线程的内核对象hThread1与hThread2 //创建一个互斥对象,如果成功返回互斥对象的句柄,否则返回NULL hMutex = CreateMutex(NULL, FALSE, "tickets"); if (hMutex) { if(ERROR_ALREADY_EXISTS == GetLastError()) { cout << "only one instance can run!" << endl; return; } } // while(index++ < 100) // { // cout << "main Thread is running!" << endl; // } Sleep(4000);//主线程睡眠4秒钟,给其它线程运行的时间,因为一旦主线程退出则进行退出,其它线程也将退出 } DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ){ // while(index++ < 100) // cout << "Thread1 is running!" + index << endl; while(TRUE){ WaitForSingleObject(hMutex, INFINITE);//如果全局互斥对象是有信号状态,则获得该对象, //直到调用ReleaseMutex之前,互斥对象是无信号状态,其它线程不能对互斥对象进行访问 if(tickets > 0) { Sleep(1); cout << "Thread1 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex);//将互斥对象设置为有信号状态 } return 0; } DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data ) { while(TRUE){ WaitForSingleObject(hMutex, INFINITE); if (tickets > 0) { Sleep(1); cout << "Thread2 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex); } return 0; }
4、多线程聊天程序
(1) 加载套接字库在InitInstance()中,调用AfxSocketInit(),此时可以不加载库文件,但要加入Afxsock.h"头文件
if (!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
(2) 在CChatDlg.h中类的声明外,创建一个全局的结构体,包含套接字和窗口的句柄值,主要是在投送消息时可以将两个需要传送的消息同时发送
struct RECVPARAM{
SOCKET socket;
HWND hWnd;
};
在CChatDlg中创建成员变量m_socket,然后增加一个成员函数,IniSocket(),在其中完成m_socket的初始化和绑定。
BOOL CChatDlg::InitSocket() { m_socket = socket(AF_INET, SOCK_DGRAM, 0); if (INVALID_SOCKET == m_socket) { MessageBox("创建套接字失败!"); return FALSE; } SOCKADDR_IN addrSock; addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSock.sin_family = AF_INET; addrSock.sin_port = htons(6010); int bindRst; bindRst = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR)); if (SOCKET_ERROR == bindRst) { closesocket(m_socket); MessageBox("绑定失败!"); return FALSE; } return TRUE; }
(1) 创建一个线程,CreateThread(),须将线程函数RecvProc定义为静态的或者全局函数。因为对于运行的代码来说,它不知道要产生哪一个对象,即运行时根本不知道如何去产生一个CChatDialog类的对象,对于运行时代码来说,如果要调用线程函数来启动某个线程的话,应该不需要产生某个对象就可以调用这个线程函数.因此要定义为类的静态成员或全局函数,即这个代码是所以对象共有的,不需要定义对象才能使用.
static DWORD WINAPI RecvProc(LPVOID lpParameter);
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter) { //根据线程函数参数获取发送端套接字和要设置包含文本控件的窗口句柄 SOCKET socket = ((RECVPARAM*)lpParameter)->socket; HWND hWnd = ((RECVPARAM*)lpParameter)->hWnd;//窗口句柄 SOCKADDR_IN addrRecv; int len = sizeof(SOCKADDR); char recvBuf[100]; char tempBuf[100]; int recvRst; CString recvStr; while(TRUE) { //接收数据 recvRst = recvfrom(socket, recvBuf, 100, 0, (SOCKADDR*)&addrRecv, &len); if (SOCKET_ERROR == recvRst) { break; } sprintf(tempBuf, "%s说:%s\r\n", inet_ntoa(addrRecv.sin_addr), recvBuf); // recvStr += tempBuf;//这种方式更加简单不用使用消息 // ::SetDlgItemText(hWnd, IDC_EDIT_RECV, recvStr); //使用自定义消息的方式来对控件填充内容 ::PostMessage(hWnd, WM_RECVDATA, 0, (LPARAM)tempBuf); } return 0; }
在OnInitDialog中调用InitSocket完成初始化工作。
InitSocket();//初始化套接字库 RECVPARAM *pRecvParm = new RECVPARAM(); pRecvParm->socket = m_socket; pRecvParm->hWnd = m_hWnd; HANDLE hThreadRecv = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParm, 0, NULL); CloseHandle(hThreadRecv);
(1) ::PostMessage()完成将收到的数据发送给对话框。用自定义的消息参考下面的代码。注意要将EDitBox的MultiLine属性选上。
<1>在ChatDlg.h中#define WM_RECVDATA WM_USER+1
<2>afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
<3>在ChatDlg.cpp中ON_MESSAGE(WM_RECVDATA,OnRecvData)
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam) { CString str=(char*)lParam; CString strTemp; GetDlgItemText(IDC_EDIT_RECV,strTemp); str+="\r\n"; str+=strTemp; SetDlgItemText(IDC_EDIT_RECV,str); }
(1) 最后在DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
中调用 ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
//不能用SendMessage()
6.对发送按纽的响应代码:
void CChatDlg::OnBtnSend() { // TOD Add your control notification handler code here DWORD dwIP; ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); 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_EDIT_SEND,strSend); sendto(m_socket,strSend,strSend.GetLength()+1,0, (SOCKADDR*)&addrTo,sizeof(SOCKADDR)); SetDlgItemText(IDC_EDIT_SEND,""); }