多线程
1.基本概念
1.1程序和进程
程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身地址空间的一次执行活动。
一个程序可以对应多个进程,一个进程中也可以同时访问多个程序。
进程的组成
1)操作系统用来管理进程的内核对象
内核是系统用来存放关于进程的统计信息的地方。内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员负责维护该对象的各种信息。
2)地址空间
进程从来不执行任何东西,真正完成代码执行的是线程,而进程只是线程的容器,或者说是线程的执行环境。单个进程可能包含若干个线程,这些线程都同时执行进程地址空间中的代码。每个进程至少拥有一个线程,来执行进程空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程,也就是执行main函数或WinMain函数的线程,此后主线程可以创建其他线程。
进程地址空间
系统赋予每个进程独立的虚拟地址空间。对于32为进程来说,这个地址空间时4GB。因为对32位指针来说,它能寻址的范围是2的32次,即4GB。
1.3线程
线程有两个部分组成:
1)线程的内核对象。
操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
2)线程栈。
用于维护线程在执行代码时需要的所有函数参数和句柄变量。系统从进程的地址空间中分配内存,供线程栈使用。新线程运行的进程环境和创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中所有堆栈,这使得单个进程中的所有线程容易的相互通信
线程运行:
操作系统为每一个运行线程安排一定的CPU时间---时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就好像多个线程同时运行一样。如果计算机拥有多个CPU,线程就能真正意义上同时运行了。
多线程和多进程
应该尽量使用多线程,原因有两个:1)一是对进程的创建来说,系统要为进程分配私有的4GB的虚拟地址空间,当然他所占用的自由比较多,而对多线程程序来说,多个线程是共享同一个进程的地址空间,所以占用的资源比较少。另一个理由是当进程间切换时,需要交换整个地址空间,而线程之间的切换只是执行环境的改变,因此效率比较高。
2.线程创建函数
HANDLE CreateThread (//创建线程函数
SEC_ATTRS SecurityAttributes,
ULONG StackSize,
SEC_THREAD_START StartFunction,
PVOID ThreadParameter,
ULONG CreationFlags,
PULONG ThreadId
);
void Sleep(//使线程暂停的函数
DWORD dwMilliseconds);
3.线程同步
3.1利用互斥对象实现线程同步
互斥对象属于内核对象,而且是唯一与线层相关的内核对象,对互斥对象来说,谁拥有谁释放,它能够确保线程拥有对单个资源的互斥访问权限。如果多次砸同一个线程中申请同一个互斥对象,那么就需要相应的多次释放互斥对象。
HANDLE CreateMutex( //创建互斥对象函数
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName );
BOOL ReleaseMutex( //释放互斥对象函数
HANDLE hMutex );
DWORD WaitForSingleObject( //请求共享对象函数
HANDLE hHandle,
4.网络聊天程序的实现(关于网络编程详见 http://blog.csdn.net/walkerkalr/article/details/19443921)
1.新建一个对话框应用程序,工程名这里chat1
2.UI设计如图
3.加载套接字库
1)在 Cchat1App::InitInstance()添加
{
if(!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
}
2)在stdafx.h中添加#include <Afxsock.h>
4.创建并初始化套接字
1)为CChatDlg类增加一个SOCKET类型的成员变量:m_socket,即套接字描述符。
2)在增加一个bool类型的成员函数InitSocket,用来初始化该类的套接字成员变量。
代码如下:
在OnInitDialog中调用InitSocket();
5.实现接受功能
1)在CChatDialog类头文件中,在该类的声明的外部定义一个RECVPARAM结构体。这里定义结构体是因为接
受数据的线程需要两个参数,一个以创建的套接字,一个对话框控件的句柄。
struct RECVPARAM
{
SOCKET sock;
HWND hwnd;
};
2)在CChatDialog类的OnInitDialog函数中,在上面刚刚添加的InitSocket函数调用如下代码,已完成数据
接受线程的创建,并传递所需参数。
3)接受线程入口函数的编写
首先把线程函数声明(线程函数不应该与类对象有关联)为类的静态函数,即在CChatDialog类头文件中添
加:
static DWORD WINAPI RecvProc(LPVOID lpParameter);
在CChatDialog源文件中加:
4)编写ReveData消息响应函数
在CChatDialog类头文件中添#define WM_RECVDATA WM_USER+1
afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
然后再在源文件中加:
ON_MESSAGE(WM_RECVDATA,OnRecvData)
6实现发送端功能
1)实现发送按钮响应函数
最后将“发送按钮改为”属性默认按钮,接受数据框改成多行显示。
8.测试
1.基本概念
1.1程序和进程
程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身地址空间的一次执行活动。
一个程序可以对应多个进程,一个进程中也可以同时访问多个程序。
进程的组成
1)操作系统用来管理进程的内核对象
内核是系统用来存放关于进程的统计信息的地方。内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员负责维护该对象的各种信息。
2)地址空间
进程从来不执行任何东西,真正完成代码执行的是线程,而进程只是线程的容器,或者说是线程的执行环境。单个进程可能包含若干个线程,这些线程都同时执行进程地址空间中的代码。每个进程至少拥有一个线程,来执行进程空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程,也就是执行main函数或WinMain函数的线程,此后主线程可以创建其他线程。
进程地址空间
系统赋予每个进程独立的虚拟地址空间。对于32为进程来说,这个地址空间时4GB。因为对32位指针来说,它能寻址的范围是2的32次,即4GB。
1.3线程
线程有两个部分组成:
1)线程的内核对象。
操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
2)线程栈。
用于维护线程在执行代码时需要的所有函数参数和句柄变量。系统从进程的地址空间中分配内存,供线程栈使用。新线程运行的进程环境和创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中所有堆栈,这使得单个进程中的所有线程容易的相互通信
线程运行:
操作系统为每一个运行线程安排一定的CPU时间---时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就好像多个线程同时运行一样。如果计算机拥有多个CPU,线程就能真正意义上同时运行了。
多线程和多进程
应该尽量使用多线程,原因有两个:1)一是对进程的创建来说,系统要为进程分配私有的4GB的虚拟地址空间,当然他所占用的自由比较多,而对多线程程序来说,多个线程是共享同一个进程的地址空间,所以占用的资源比较少。另一个理由是当进程间切换时,需要交换整个地址空间,而线程之间的切换只是执行环境的改变,因此效率比较高。
2.线程创建函数
HANDLE CreateThread (//创建线程函数
SEC_ATTRS SecurityAttributes,
ULONG StackSize,
SEC_THREAD_START StartFunction,
PVOID ThreadParameter,
ULONG CreationFlags,
PULONG ThreadId
);
void Sleep(//使线程暂停的函数
DWORD dwMilliseconds);
3.线程同步
3.1利用互斥对象实现线程同步
互斥对象属于内核对象,而且是唯一与线层相关的内核对象,对互斥对象来说,谁拥有谁释放,它能够确保线程拥有对单个资源的互斥访问权限。如果多次砸同一个线程中申请同一个互斥对象,那么就需要相应的多次释放互斥对象。
HANDLE CreateMutex( //创建互斥对象函数
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName );
BOOL ReleaseMutex( //释放互斥对象函数
HANDLE hMutex );
DWORD WaitForSingleObject( //请求共享对象函数
HANDLE hHandle,
DWORD dwMilliseconds );
#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=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,false,NULL);
Sleep(4000);
}
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;
}
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;
}
}
4.网络聊天程序的实现(关于网络编程详见 http://blog.csdn.net/walkerkalr/article/details/19443921)
1.新建一个对话框应用程序,工程名这里chat1
2.UI设计如图
3.加载套接字库
1)在 Cchat1App::InitInstance()添加
{
if(!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
}
2)在stdafx.h中添加#include <Afxsock.h>
4.创建并初始化套接字
1)为CChatDlg类增加一个SOCKET类型的成员变量:m_socket,即套接字描述符。
2)在增加一个bool类型的成员函数InitSocket,用来初始化该类的套接字成员变量。
代码如下:
BOOL CChatDlg::InitSocket()
{
m_socket=socket(AF_INET,SOCK_DGRAM,0);
if(INVALID_SOCKET==m_socket)
{
MessageBox("套接字创建失败!");
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;
retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if(SOCKET_ERROR==retval)
{
closesocket(m_socket);
MessageBox("绑定失败!");
return FALSE;
}
return TRUE;
}
在OnInitDialog中调用InitSocket();
5.实现接受功能
1)在CChatDialog类头文件中,在该类的声明的外部定义一个RECVPARAM结构体。这里定义结构体是因为接
受数据的线程需要两个参数,一个以创建的套接字,一个对话框控件的句柄。
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)接受线程入口函数的编写
首先把线程函数声明(线程函数不应该与类对象有关联)为类的静态函数,即在CChatDialog类头文件中添
加:
static DWORD WINAPI RecvProc(LPVOID lpParameter);
在CChatDialog源文件中加:
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
SOCKET sock=((RECVPARAM*)lpParameter)->sock;
HWND hwnd=((RECVPARAM*)lpParameter)->hwnd;
delete lpParameter; //视频讲述时,遗忘了释放内存的操作。sunxin
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;
}
4)编写ReveData消息响应函数
在CChatDialog类头文件中添#define WM_RECVDATA WM_USER+1
afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
然后再在源文件中加:
ON_MESSAGE(WM_RECVDATA,OnRecvData)
6实现发送端功能
1)实现发送按钮响应函数
void Cchat1Dlg::OnBnClickedBtnSend()
{
// TODO: Add your control notification handler code here
//获取对方IP
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,"");
}
7.改善
最后将“发送按钮改为”属性默认按钮,接受数据框改成多行显示。
8.测试
127.0.0.1是回送地址,指本地机
==本文参考VC++深入详解
==如需转载,请注明出处,谢谢!