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

多线程编程的一点小心得(1) 推荐

2012-05-08 17:45 429 查看
最近有了很多想法,想把我用过的东西都吃透,这样才不会变成所谓的“样样通样样松”。我是新手,老鸟请飘过,当然,这篇小心得如果有什么毛病,还请指出来。先行谢过!

其实我本来想把博客当作自己的日记,记录下学习的点点滴滴,写下的就代表是学会的东西,人家说好记性不如烂笔头嘛。

一直以来对多线程这块就迷迷糊糊的,用得不太多,即便是用了,也是把以前写的代码拿出来,稍微修改一下,就适应了新的需求。也看过一些资料,但都没实践过,所以就马马虎虎地,能够适应工作需求就得过且过。其实这种思想是非常错误的。做技术一定要踏实,否则就无法成长。

闲话少说书归正传,接下来的几篇就把多线程的东西学习、总结一下。先上一段万金油:

线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,那就是程序本身。

自从有了线程,这个世界就变得吵起来了,线程的周期、调度与优先级、资源共享、线程同步、守护线程、死锁、信号量。。后面咱们再慢慢研究这些东西吧,今天先来简单明白线程到底是什么,怎么应用在编程中。

老惯例,上个程序吧。建立一个标准的基于对话框的MFC程序,拖一个edit控件和一个button在上面,资源命名分别为ID_EDIT_NUMBER和ID_BUTTON_START。给edit控件关联一个变量CEdit * m_editNumber。弄差不多这个样子就行。





先写一个线程函数,告诉电脑在这个线程里要做什么。

DWORD _stdcall ThreadProc(LPVOID lpParameter)
{
CMultithreadTestDlg * dlg = (CMultithreadTestDlg*) lpParameter;
CString szCounter;

for(int i = 0; i < 10000; i++)
{
szCounter.Format(_T("%d"), i);
dlg->m_editNumber.SetWindowTextW(szCounter);
szCounter.ReleaseBuffer();
}

return 0;
}

很简单,就是让edit控件显示不停增加的数字。

接下来就要想办法启动这个线程。

在CMultithreadTestDlg中添加一个成员变量HANDLE m_hThread。双击start按钮,编写按钮的单击事件。

void CMultithreadTestDlg::OnBnClickedButtonStart()
{
// TODO: Add your control notification handler code here
m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, NULL);
}

CreateThread函数的原型如下:

HANDLE WINAPI CreateThread(
__in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in       SIZE_T dwStackSize,
__in       LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt   LPVOID lpParameter,
__in       DWORD dwCreationFlags,
__out_opt  LPDWORD lpThreadId
);

第一个参数是安全属性,指向一个LPSECURITY_ATTRIBUTES类型的结构体,一般设为NULL;

第二个参数是线程的堆栈大小,如果不是内存特别紧张的话,就设为0,表示windows将动态调整堆栈的大小;

第三个参数是指向线程函数的指针,其实就是函数名。函数名随便起,但是在声名函数时必须要遵守形式

DWORD WINAPI ThreadProc(LPVOID lpParameter)

否则就无法成功调用;

第四个参数是向线程函数传递的参数,不传递时就设为NULL;

第五个参数是线程标志,有两个可取值:

1). CREATE_SUSPENDED,表示创建后立即挂起

2). 0,表示正常创建,创建后立刻运行

第六个参数用来保存新建线程的ID,如果不需要处理线程ID的话,则可传入NULL。

返回值是线程的句柄。

运行时效果如下





这个程序其实是有风险的,风险有二:

1). 在MFC程序中,应该尽量使用AfxBeginThread方法来创建线程。

2). 如果我不停地按start,一会内存就用光了=。=

2的解决方法就不上了,无非是使用标志位,线程没跑完之前不再创建新的线程。

来说说AfxBeginThread。这是MFC中的比较安全的线程创建方法。函数原型如下:

CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);

有两个可以重载的函数,常用的是第一个。也能看出来,第一个函数与CreateThread()的参数其实是差不多的,只不过顺序不太一样。需要注意的是第二个重载函数,参数一是CRuntimeClass * pThreadClass,CRuntimeClass是个结构体,MSDN里的解释是“The RUNTIME_CLASS of an object derived from CWinThread.”为此我特意看了一下AfxBeginThread的源代码,其中有如下一行:

ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread)));

表明RUNTIME_CLASS是个宏定义。

#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())

也就是用这个宏将线程类指针转换为指向CRuntimeClass的对象指针。
那么新的线程创建语句就变为了:

CWinThread * m_thread;    // m_thread为成员变量

m_thread = AfxBeginThread(ThreadProc, this);

而且需要将线程函数的声明修改一下:
UINT ThreadProc(LPVOID lpParameter)

线程执行的中间是可以暂停的,使用DWORD CWinThread::SuspendThread()函数即可。暂停后可以使用DWORD CWinThread::ResumeThread()函数使线程恢复运行。
这是基本用法,至于一些高级点儿的东西,明儿继续。
PS:正所谓懂得越多就发现懂得越少,今天搜资料,又搜出好多没听过的东西=。=,全部记在本子上,逐个消灭之。。
PSS:下一个目标,看明白与这个网页相关联的东西。。http://en.wikipedia.org/wiki/Thread_(computing)
PSSS:这玩意儿真形象。。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MFC 线程