多线程基本概念及编程实现

多线程
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 );


3.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=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++深入详解

==如需转载,请注明出处,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值