Windows线程使用

1.内核对象

操作系统创建的资源有很多种,如进程、线程、文件及信号量、互斥量等。其中大部分都是通过程序员的请求创建的,而且请求方式(请求中使用的函数)各不相同。虽然存在一些差异,但他们之间也有共同点:都是由操作系统创建并管理的资源。

不同资源类型在“管理”方式上也有差异。例如:文件管理中应注册并更新文件相关的数据I/O位置、文件的打开模式等。如果是线程,则应注册并维护线程ID、线程所属进程等信息。操作系统为了以记录相关信息的方式管理各种资源,在其内部生成数据块(可视为结构体变量)。每种资源需要维护的信息不同,所以每种资源拥有的数据块格式也有差异。这类数据块称为“内核对象”。

注意:内核对象的所有者是内核(操作系统),其含义为:内核对象的创建、管理、销毁时机的决定等工作均由操作系统完成。

2.基于Windows的线程创建

windows创建线程的API原型如下:

#include <windows.h>
HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,      // pointer to security attributes  
    DWORD dwStackSize,                             // initial thread stack size  
    LPTHREAD_START_ROUTINE lpStartAddress,         // pointer to thread function  
    LPVOID lpParameter,                            // argument for new thread  
    DWORD dwCreationFlags,                         // creation flags  
    LPDWORD lpThreadId                             // pointer to receive thread ID
);

参数看起来有些复杂,但只需考虑lpStartAddress和lpParameter这两个参数,剩下的传递0或NULL即可。

windows线程的销毁时间点:
windows线程在首次调用的线程main返回时销毁。还有其它方法可以终止线程,但最好的办法就是让main函数终止(返回)。

如果线程要调用C/C++标准函数,需要通过如下方法创建线程。因为通过CreateThread函数调用创建出的线程在使用C/C++标准函数时并不稳定。

#include <process.h>  
uintptr_t _beginthreadex(  
    void * security,                     //线程安全相关信息,使用默认设置时传递NULL  
    unsigned stack_size,                 //要分配给线程的栈大小,传递0时生成默认大小的栈  
    unsigned (* start_address)(void *),  //传递线程的main函数信息  
    void * arglist,                      //调用main时传递的参数信息  
    unsigned initflag,                   //用于指定线程创建后的行为,传递0时,线程创建后立即进入可执行状态  
    unsigned * thrdaddr                  //用于保存线程ID的变量地址值  
    );  
//成功返回线程句柄,失败返回0

这个函数和CreateThread相比,参数个数以及参数的含义和顺序均相同,只是变量名和参数类型有所不同。用上面的函数替换CreateThread时,只需适当更改数据类型。

_beginthread函数:
这个函数比_beginthreadex更好用,但该函数的问题在于,它会让创建线程时返回的句柄失效,以防止访问内核对象。_beginthreadex就是为了解决这一问题而定义的函数。

#include <windows.h>
#include <iostream>
#include <process.h>
unsigned WINAPI ThreadFunc(void *arg);

int main(int argc, char *argv[])
{
    HANDLE hThread;  //线程句柄
    unsigned threadID;  //线程ID
    int param = 5;
    hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)param, 0, &threadID);
    if(0 == hThread)
    {
        std::cout << "_beginthreadex() error" << std::endl;
        return -1;
    }
    Sleep(3000);
    std::cout << "end of main" << std::endl;

    return 0;
}

unsigned WINAPI ThreadFunc(void *arg)
{
    int cnt = *((int*)arg);
    for(int i = 0; i < cnt; i++)
    {
        Sleep(1000);
        std::cout << "running thread" << std::endl;
    }
    return 0;
}

句柄、内核对象和ID间的关系:
线程也属于操作系统管理的资源,因此会伴随内核对象的创建,并为了引用内核对象而返回句柄。
可以通过句柄区分内核对象,通过内核对象区分线程。最终,线程句柄成为区分线程的工具。通过_beginthreadex函数的最后一个参数可以获取线程ID。句柄和ID有如下显著特点:
句柄的整数值在不同进程中可能出现重复,但线程ID在跨进范围内不会重复。

线程ID用于区分操作系统创建的所有线程,但通常没有这种需求。

内核对象的2种状态:
资源类型不同,内核对象也含有不同信息。其中,应用程序实现过程中需要特别关注的信息被赋予某种“状态”。例如:线程内核对象中需要重点关注线程是否已终止,所以终止状态又称为“signaled状态”,未终止状态被称为“non-signaled状态”。

内核对象的状态及状态查看:
进程和线程的内核对象初始状态是non-signaled状态。内核对象带有一个boolean变量,其初始值为FALSE,此时的状态就是non-signaled状态。如果发生了事件,把该变量变为TRUE,此时的状态就是signaled状态。内核对象类型不同,进入signaled的状态也有所区别。

系统还定义了WaitForSingleObject和WaitForMultipleObjects两个函数。

首先介绍WaitForSingleObject函数,该函数针对单个内核对象验证signaled状态。

DWORD WaitForSingleObject(
    HANDLE hHandle,  //handle to object to wait for
    DWORD dwMilliseconds  //time-out interval in milliseconds
);
//返回值:进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT

该函数由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次更改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象称为“auto-reset模式”的内核对象,而不会自动跳转到non-signaled状态的内核对象称为“manual-reset模式”。下面的函数与上述函数不同,可以验证多个内核对象状态。

DWORD WaitForMultipleObjects(
    DWORD nCount,  // number of handles in the handle array  
    CONST HANDLE *lpHandles,  // pointer to the object-handle array  
    BOOL fWaitAll,  // wait flag  
    DWORD dwMilliseconds  // time-out interval in milliseconds
);

修改后的程序:

    #include <windows.h>  
    #include <iostream>  
    #include <process.h>  
    unsigned WINAPI ThreadFunc(void *arg);  

    int main(int argc, char *argv[])  
    {  
        HANDLE hThread;       //线程句柄  
        DWORD wr;  
        unsigned threadID;    //线程ID  
        int param = 5;  
        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)param, 0, &threadID);  
        if (0 == hThread)  
        {  
            std::cout << "_beginthreadex() error" << std::endl;  
            return -1;  
        }  

        //传递INFINITE时函数不会返回,直到内核对象变成signaled状态  
        if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)     
        {  
            std::cout << "thread wait error" << std::endl;  
            return -1;  
        }  
        std::cout << "wait result:" << ((wr == WAIT_OBJECT_0) ? "signed" : "time-out");  
        std::cout << std::endl;  
        std::cout << "end of main" << std::endl;  

        return 0;  
    }  

    unsigned WINAPI ThreadFunc(void *arg)  
    {  
        int cnt = *((int*)arg);  
        for (int i = 0; i < cnt; i++)  
        {  
            Sleep(1000);    
            std::cout << cnt << "running thread" << std::endl;  
        }  
        return 0;  
    }  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • LINUX 基础知识 1、线程的概念 上下文切换 : 运行程序前需要将相应进程信息读入内存,如果运行进程A后需要紧...
    Ycres阅读 752评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 老婆煮了点粥,问我饿不饿。我摇了摇头,没有一丝胃口,我打开电视机想看看新闻,电视机里除了泡沫剧就是抗日剧,每个台底...
    大黄蜂_a757阅读 569评论 0 2
  • 本文重点针对android TV开发的同学,分析遥控或键盘按键事件后焦点的分发机制。尤其是刚从手机开发转向TV开发...
    wanderingGuy阅读 2,553评论 0 5