• 线程基础


    线程有两部分组成

    (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

  • 相关阅读:
    C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理
    C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理
    C#开发微信门户及应用(16)-微信企业号的配置和使用
    C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能
    会员管理系统的设计和开发(3)--主界面的设计思路分享
    会员管理系统的设计和开发(2)-- RDLC报表的设计及动态加载
    Winform开发中常见界面的DevExpress处理操作
    在WCF数据访问中使用缓存提高Winform字段中文显示速度
    双指针算法模板和一些题目
    尾递归 递归函数中,递归调用是整个函数体中最后的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归,空间复杂度是O(1)
  • 原文地址:https://www.cnblogs.com/CBDoctor/p/3066011.html
Copyright © 2020-2023  润新知