windows多线程编程(一)(转)

源出处:http://www.cnblogs.com/TenosDoIt/archive/2013/04/15/3022036.html

CreateThread:Windows的API函数(SDK函数的标准形式,直截了当的创建方式,任何场合都可以使用),提供操作系统级别的创建线程的操作,且仅限于工作者线程

beginthread beginthreadex:MS对C Runtime库的扩展SDK函数,首先针对C Runtime库做了一些初始化的工作,以保证C Runtime库工作正常,然后,调用CreateThread真正创建线程。beginthread是_beginthreadex的功能子集,虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,例如,如果使用_beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代

AfxBeginThread:MFC中线程创建的MFC函数,它简化了操作或让线程能够响应消息,即可用于界面线程,也可以用于工作者线程,但要注意尽量不要在一个MFC程序中使用_beginthreadex()或CreateThread()。

AfxBeginThread、BeginThread和BeginThreadex实际上是编译器对CreateThread的封装

.编程的时候如何选择各个函数

MFC程序选择AfxBeginThread当然不容置疑

如果不使用Microsoft的Visual   C++编译器,你的编译器供应商有它自己的CreateThred替代函数

尽量不要调用CreateThread。相反,应该使用Visual C++运行期库函数_beginthreadex,原因如下:

考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于多线程会出什么事,你一定知道的了。故必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址(自己看去吧)。   
  通过线程局部存储将tiddata与线程联系起来。具体实现在Threadex.c中有。   
  结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。   
  CRT的函数库在线程出现之前就已经存在,所以原有的CRT不能真正支持线程,这导致我们在编程的时候有了CRT库的选择,在MSDN中查阅CRT的函数时都有:   
  Libraries   
  LIBC.LIB   Single   thread   static   library,   retail   version     
  LIBCMT.LIB   Multithread   static   library,   retail   version     
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version     
  这样的提示!   
  对于线程的支持是后来的事!   
  这也导致了许多CRT的函数在多线程的情况下必须有特殊的支持,不能简单的使用CreateThread就OK。   
  大多的CRT函数都可以在CreateThread线程中使用,看资料说只有signal()函数不可以,会导致进程终止!但可以用并不是说没有问题!   
  有些CRT的函数象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有Memory leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!   
  _beginthreadex和_endthreadex就对这个内存块做了处理,所以没有问题!(不会有人故意用CreateThread创建然后用_endthreadex终止吧,而且线程的终止最好不要显式的调用终止函数,自然退出最好!)

如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex和_endthreadex: 
1 使用malloc()和free(),或是new和delete 
2 使用stdio.h或io.h里面声明的任何函数 
3 使用浮点变量或浮点运算函数 
4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand() 
  
  Handle的问题,_beginthread的对应函数_endthread自动的调用了CloseHandle,而_beginthreadex的对应函数_endthreadex则没有,所以CloseHandle无论如何都是要调用的不过_endthread可以帮你执行自己不必写,其他两种就需要自己写!(Jeffrey   Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写CloseHandle)



转载自C++多线程实例(_beginThreadex创建多线程)

二解释 
      1)如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用VisualC++运行期库函数_beginthreadex,推出也应该使用_endthreadex。如果不使用Microsoft的VisualC++编译器,你的编译器供应商有它自己的CreateThred替代函数。不管这个替代函数是什么,你都必须使用。

2)因为_beginthreadex和_endthreadex是CRT线程函数,所以必须注意编译选项runtimelibaray的选择,使用MT或MTD。

3) _beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同。这是因为Microsoft的C/C++运行期库的开发小组认为,C/C++运行期函数不应该对Windows数据类型有任何依赖。_beginthreadex函数也像CreateThread那样,返回新创建的线程的句柄。 
下面是关于_beginthreadex的一些要点: 
            &8226;每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的VisualC++源代码中)。

&8226;传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。

&8226;_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。

&8226;当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。

&8226;如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。

4) _endthreadex的一些要点: 
          &8226;C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。

&8226;然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。

5)虽然也提供了简化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。

6)线程handle因为是内核对象,所以需要在最后closehandle。

7)更多的API:

HANDLE GetCurrentProcess();

HANDLE GetCurrentThread();

DWORD GetCurrentProcessId();

DWORD GetCurrentThreadId()。

DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);

BOOL SetThreadPriority(HANDLE hThread,int nPriority);

BOOL SetPriorityClass(GetCurrentProcess(),  IDLE_PRIORITY_CLASS);

BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread(); 
三注意 
1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。所以上面的代码中如果不调用WaitForSingleObject,则2个子线程t1和t2可能并没有执行完毕或根本没有执行。 
2)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。所以上面的代码如果不调用resumethread,则会死锁。

为什么要用C运行时库的_beginthreadex代替操作系统的CreateThread来创建线程?

来源自自1999年7月MSJ杂志的《Win32 Q&A》栏目

你也许会说我一直用CreateThread来创建线程,一直都工作得好好的,为什么要用_beginthreadex来代替CreateThread,下面让我来告诉你为什么。 
回答一个问题可以有两种方式,一种是简单的,一种是复杂的。 
如果你不愿意看下面的长篇大论,那我可以告诉你简单的答案:_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全。



_beginthreadex用法

头文件:process.h

函数原型:unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );

//第1个参数:安全属性,NULL为默认安全属性 
       //第2个参数:指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0 
       //第3个参数:指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址,注意的是函数访问方式一定是__stdcall,函数返回值一定是unsigned,函数参数一定是void*) 
       //第4个参数:传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针 
       //第5个参数:线程初始状态,0:立即运行;CREATE_SUSPEND:悬挂(如果出事状态定义为悬挂,就要调用ResumeThread(HANDLE) 来激活线程的运行) 
      //第6个参数:用于记录线程ID的地址

使用举例


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

#include<string>

 #include<iostream>

 #include<process.h>

 #include<windows.h>

 using namespace std;

struct Arg//用来传参给线程函数

{

    double d_;

    string str_;

    Arg(double dd, string ss):d_(dd), str_(ss){}

};

//线程绑定的函数返回值和参数是确定的,而且一定要__stdcall

unsigned __stdcall threadFun(void *)

{

    for(int i = 0; i < 10; i++)

        cout<<i<<endl;

    return 1;

}

//可以通过结构体来传入参数

unsigned __stdcall threadFunArg(void *arglist)

{

    Arg *p = (Arg *)arglist;

    cout<<p->d_<<endl;

    cout<<p->str_<<endl;

    return 2;

}

//简单的线程类

class ThreadClass

{

private:

    string str_;

    int i_;

public:

    ThreadClass(string s, int i):str_(s), i_(i){}

    static unsigned __stdcall threadStaic(void *arg)

    {

        ThreadClass *p = (ThreadClass *)arg;

        p->threadfun();

        return 3;

    }

    void threadfun()

    {

        cout<<str_<<endl;

        cout<<i_<<endl;

    }

};

int main()

{

    unsigned int thID1, thID2, thID3, thID4;

    HANDLE hth1, hth2, hth3, hth4;

    Arg arg(3.14, "hello world");

    ThreadClass tclass("welcom", 999);

    //注意的是_beginthreadex是立即返回的,系统不会等线程函数执行完毕,因此要保证

    //局部arg变量 在线程函数执行完毕前不会释放,更安全的是使用new来构造arg

    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID1);

    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID2);

    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunArg, &arg, 0, &thID3);

    hth4 = (HANDLE)_beginthreadex(NULL, 0, ThreadClass::threadStaic, &tclass, 0,

                                   &thID4);

    //主线程一定要等待子线程结束

    WaitForSingleObject(hth1, INFINITE);

    WaitForSingleObject(hth2, INFINITE);

    WaitForSingleObject(hth3, INFINITE);

    WaitForSingleObject(hth4, INFINITE);

    DWORD exitCode1, exitCode2, exitCode3, exitCode4;

    GetExitCodeThread(hth1, &exitCode1);

    GetExitCodeThread(hth2, &exitCode2);

    GetExitCodeThread(hth3, &exitCode3);

    GetExitCodeThread(hth4, &exitCode4);

    cout<<endl<<"exitcode::"<<exitCode1<<" "<<exitCode2<<" "<<exitCode3<<" "

        <<exitCode4<<endl;

    cout<<"ID:"<<thID1<<" "<<thID2<<" "<<thID3<<" "<<thID4<<endl;

    //一定要记得关闭线程句柄

    CloseHandle(hth1);

    CloseHandle(hth2);

    CloseHandle(hth3);

    CloseHandle(hth4);

}

时间: 2024-07-29 16:21:26

windows多线程编程(一)(转)的相关文章

Windows多线程编程总结

Windows 多线程编程总结 keyword:多线程 线程同步 线程池 内核对象 1 内核对象 1 .1 内核对象的概念 内核对象是内核分配的一个内存块,这样的内存块是一个数据结构,表示内核对象的各种特征.而且仅仅能由内核来訪问.应用程序若须要訪问内核对象,须要通过操作系统提供的函数来进行,不能直接訪问内核对象( Windows 从安全性方面来考虑的). 内核对象通过 Create* 来创建,返回一个用于标识内核对象的句柄,这些句柄 (而不是内核对象)可在创建进程范围内使用,不可以被传递到其它

Windows多线程编程及常见问题

提要: Windows 多线程Helloworld 以Windows代码为例,分析多线程编程中易出现的问题 Windows多线程的Helloworld: 笔者写过Java多线程的程序(实现Runnable接口,利用Thread类执行),也写过Linux多线程程序(利用pthread).最近由于另有需要使用Windows多线程,由于Windows API历来难用,特此记录,以作备忘. Helloworld源代码如下: 1 #include <stdio.h> 2 #include <win

【APUE】关于windows多线程编程的学习笔记

保证在某一时刻只有一个线程对数据进行操作的基本方法: (1)关中断:通过关闭时钟中断来停止线程调度(不现实) (2)数学互斥方法:Peterson算法 bakery算法 (3)操作系统提供的互斥方法:临界区.互斥量.信号量等(windows) (4)cpu原子操作:把一些常用的指令设计成了原子指令,在windows上面也被称为原子锁 [APUE]关于windows多线程编程的学习笔记

windows多线程编程

进程共同实现某个任务或者共享计算机资源, 它们之间存在两种关系: 1.同步关系, 指为了完成任务的进程之间, 因为需要在某些位置协调它们的执行顺序而等待, 传递消息产生的制约关系. 2.互斥关系, 进程间因相互竞争使用独占型资源所产生的制约关系, 如一个进程使用打印机,另一个进程必须等待它使用完后才可使用. 临界资源: 一次仅允许一个进程使(必须互斥使用)的资源, 如独占型硬件资源..... 临界段: 指各进程必须互斥执行的程序段, 该程序段实施对临界资源的操作. 关键区对象为:CRITICAL

Windows下多线程编程(一)

前言 熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. 进程与线程 1. 进程的概念 进程就是正在运行的程序.主要包括两部分: • 一个是操作系统用来管理进程的内核对象.内核对象也是系统用来存放关于进程的统计信息的地方. • 另一个是地址空间,它包含所有可执行模块或 D L L模块的代码和数据.它还包含动态内 2. 线程的概念 线程就是描述进程的一条执

Windows下多线程编程

熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. 进程与线程 1. 进程的概念 进程就是正在运行的程序.主要包括两部分: ? 一个是操作系统用来管理 ... bbs.chinaacc.com/forum-2-3/topic-5662298.html bbs.chinaacc.com/forum-2-3/topic-5662294.html bbs.ch

C语言使用pthread多线程编程(windows系统)二

我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你最好是选择POSIX中的Pthread函数库,我的程序是在Linux下写的,所以我使用了Pthread库(是不是很伤心,我知道有不少人期待的是WindowsAPI的,好吧,有机会以后再讲那个,现在先把这一系列专题写完 ^_^)如果你用的是LINUX/UNIX/MacOSX,那么我们已经可以开始了,如果你用的是WINDOWS,那么你需要从网站上下载

【转】Windows的多线程编程,C/C++

在Windows的多线程编程中,创建线程的函数主要有CreateThread和_beginthread(及_beginthreadex). CreateThread 和 ExitThread    使用API函数CreateThread创建线程时,其中的线程函数原型:  DWORD WINAPI ThreadProc(LPVOID lpParameter);在线程函数返回后,其返回值用作调用ExitThread函数的参数(由系统隐式调用).可以使用GetExitCodeThread函数获得该线程

【转】《windows核心编程》读书笔记

这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLastError可能用于描述成功的原因(CreatEvent)