线程基础

线程有两部分组成

(1) 线程内核对象

操作系统利用线程内核对象来管理线程。内核对象维护着线程的统计信息。

(2) 线程栈

线程栈维护着该线程内所有的函数参数和局部变量。

进程和线程的区别之处在于:进程从来不执行任何的东西,他只是为线程的执行提供一个容器。也就是说,所有的线程都必须在某个进程内运行,我们也称此为设备的上下文。这意味:

假如一个进程上下文中有两个或两个以上的线程在运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,处理相同的数据。另外,这些线程共享 内核对象句柄,因为句柄表是针对每个进程的,而不是线程。

    相较于线程,进程所使用的系统资源比较多,其原因在于地址空间。为每一个进程创建虚拟的地址空间需要大量的系统资源,系统会有很多的记录和活动。而线程只有一个线程内核对象和线程栈。

6.2 何时不应该创建线程

    应用程序应该有一个用户界面线程,负责创建所有的窗口。用户界面线程的优先级应该高于工作线程,只有这样,才能保证用户界面迅速的响应用户的操作。

6.3 编写第一个线程函数

    线程函数可以执行我们希望它执行的任何任务。最终,线程函数将终止并返回。此时,线程终止运行,用于线程栈的内核对象也会被释放。线程内核对象的使用计数也会递减,线程内核对象的寿命至少可以达到与他们相关的线程那样长,不过线程内核对象的寿命可能超过线程本身的寿命。

使用线程函数需要注意的是:线程函数应该尽量使用函数参数和局部变量。 使用全局变量或者静态变量时,多个线程可以同时访问这些变量,这样会破坏变量的安全性。由于函数的参数和局部变量是在线程栈上创建的。因此,不会被其他线程破坏。

我们画一个示意图:

 6.4 CreateThread函数

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

调用该函数会创建一个内核对象,这个线程内核对象并不是线程本身。而是一个较小的数据结构,操作系统利用该结构管理线程。

      系统会从进程的地址空间中分配内存给线程栈使用。新线程与负责创建的那个线程在相同的设备上下文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所有线程中的栈。这样,同一个进程中的多个线程可以很容易的相互通信。

    有一点需要注意:CreateThread函数是用于创建线程的Windows函数,如果写的是C++运行库函数,就不要使用CreateThread而是使用_beginthreadex.具体的原因好象是因为CreateThread函数使用的是Windows的SDK(API)函数,使用了一些全局变量,而C++运行库函数(例如MFC)对这些全局变量进行了封装。具体的原因,请参见参考链接。

6.5 参数介绍

关于每个参数的意义,参见MSDN

Windows是抢占式多线程系统。我们举一个例子,代码描述如下:

CString str = "CYW";
void CMyMFCDlg::OnBegin() 
{
    // TODO: Add your control notification handler code here
    AfxBeginThread(MyFunc,this);
    MessageBox(str);
}

UINT CMyMFCDlg::MyFunc(LPVOID p )
{
    CMyMFCDlg* myDlg= (CMyMFCDlg*)p;
    Sleep(1000);
    str = "SHILI";
    myDlg->MessageBox(str);
    return 0;
}

观察输出的结果。另外,如果不加Sleep(1000),观察结果是怎样的!
因此,对全局变量而言,我们如果要保证变量的稳定,需要使用线程同步技术。

6.6关于几个线程函数调用的函数

创建线程的函数有很多种, 包括CreateThread、_beginthread、 _beginthreadex和AfxBeginThread. 用法在MSDN上都有详述, 这里做个笔记摘录.

1, CreateThread是不安全的,很多参考书上,都说不要用CreateThread 创建线程、并用CloseHandle来关闭这个线程,因为一些技术性的问题,会导致内存泄漏. 直接在CreateThread API创建的线程中使用sprintf,malloc,strcat等涉及CRT存储堆操作的CRT库函数是不安全的.
2, _beginthread和_beginthreadex在CreateThread上做了改进,可以安全的进行操作但是,必须在线程结束的时候相应的调用_endthread或_endthreadex.
3, beginthreadex比_beginthread更安全一些,beginthread隐式调用了CloseHandle关闭了线程句柄,而与_beginthreadex成对使用的_endthreadex则没有关闭线程的句柄,需要显示调用CloseHandle来关闭线程句柄,从逻辑上更安全.
4, 在MFC程序中,使用AfxBeginThread.
5, 在非MFC程序中,尽量使用_beginthreadex.

 6.7 终止运行线程

(1) 线程函数返回;(推荐)

(2)调用ExitThread函数杀死

(3)在同一个进程或另一个进程的线程调用TerminateThread;

(4)线程所在的进程终止;

[1]http://www.cnblogs.com/lgxqf/archive/2009/02/10/1387480.html

[2]http://support.microsoft.com/kb/104641/en-us

原文地址:https://www.cnblogs.com/CBDoctor/p/3066011.html