您的位置:首页 > 编程语言

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

2014-02-19 23:39 281 查看
多线程

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.网络聊天程序的实现(关于网络编程详见/article/1567616.html)

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

==如需转载,请注明出处,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: