https://blog.csdn.net/cbnotes/article/details/8331632

还可以看这个网址的内容:【多线程】VC6使用_beginthread开启多线程的方法-技术宅的结界 - Powered by Discuz!.html(https://www.0xaa55.com/technews/201509/00000092.html

网页内容保存:

1、CRT简介:

CRT: (C Runtime Library)即C运行时库,是系统运行的基础,包含了c常用的函数集(如:printf,malloc,strcpy等),为运行main做了初始化环境变量、堆、io等资源,并在结束后清理。

在Windows环境下,VC提供的 C run-time library又分为动态运行时库、静态运行时库、多线程、单线程、调试版本(Debug)、发行版本(Release)等。

开关

对应的库

版本

/MD

MSVCRT.LIB

多线程DLL的Release版本

/MDd

MSCVRTD.LIB

多线程DLL的Debug版本

/MT

LIBCMT.LIB

多线程静态链接的Release版本

/MTd

LIBCMTD.LIB

多线程静态链接的Debug版本

/clr

MSVCMRT.LIB

托管代码和非托管代码混合

/clr:pure

MSVCURT.LIB

纯托管代码

图1:编译器运行库设置(VS2008)

具体请参考MSDN:

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/dv_vccrt/html/a889fd39-807d-48f2-807f-81492612463f.htm

2、使用CRT的多线程函数集:两组

序号

函数名

功能

1

_beginthread()

创建一个新线程

2

_endthread()

结束一个线程的执行

3

_beginthreadex()

创建一个新线程

4

_endthreadex()

结束一个线程的执行

5

ResumeThread()

恢复线程的运行

6

SuspendThread()

挂起线程

7

GetExiCodeThread()

得到一个线程的退出码

8

WaitForSingleObject()

等待单个对象

9

WaitForMultipleObjects()

等待多个对象

3、两组函数的说明:

3.1、_beginthread和_endthread
该函数是C Runtime Library中的函数。其原型如下

unsigned long _beginthread(

void( __cdecl *start_address )( void * ),//线程函数的起始地址

unsigned stack_size,//堆栈大小,设置0为系统默认值

void *arglist );//传递给线程函数的参数,没有则为NULL

“该函数被认为是头脑简单的函数”,使用该函数导致无法有效的控制被创建线程,如不能在启动时将该线程挂起,无法为该线程设置优先权等。另外,无法利用这个Handle来等待该线程结束等操作。该函数是早期的C Runtime Library的产物,不提倡使用,后期的改良版本为_beginthreadex。
通过_beginthread启动的线程在应当通过调用_endthread结束,以保证清除与线程相关的资源。_endthread的原型为:

void _endthread(void);

3.2、_beginthreadex和_endthreadex
该函数是C Runtime Library中的一个函数,用标准C实现,相比_beginthread,_beginthreadex对线程控制更为有力(比前者多三个参数),是_beginthread的加强版。其原型为:

unsignedlong _beginthreadex(

void *security,//线程函数的安全描述符

unsigned stack_size,// 堆栈大小,设置0为系统默认值

unsigned ( __stdcall *start_address )( void * ),//线程函数的起始地址

void*arglist, //传递给线程函数的参数,没有则为NULL

unsignedinitflag,//初始状态,0为立即执行,CREATE_SUSPEND为创建后挂起

unsigned*thrdaddr );//指向一个32为的变量,存放线程标识符

该函数返回新线程的句柄,通过该句柄可实现对线程的控制。虽然,该函数是用标准C写的(即可不加修改就可以移植到其他系统执行),但是由于它与Windows系统有着紧密的联系(需要手动关闭该线程产生的Handle),因此实现时,往往需要包含windows.h。
通过_beginthreadex启动的线程通过调用_endthreadex做相关清理。该函数比较像CreateThread函数。

_endthreadex函数的原型为:

void _endthreadex(unsigned retVal);

关于这两组函数的详细区别请参考MSDN的说明:

Creates a thread.

uintptr_t _beginthread(

void( *start_address )( void * ),

unsigned stack_size,

void *arglist

);

uintptr_t _beginthreadex(

void *security,

unsigned stack_size,

unsigned ( *start_address )( void * ),

void *arglist,

unsigned initflag,

unsigned *thrdaddr

);

Parameters

start_address

Start address of a routine that begins execution of a new thread.For _beginthread, the calling convention is either__cdecl or __clrcall; for _beginthreadex, it iseither __stdcall or __clrcall.

stack_size

Stack size for a new thread or 0.

arglist

Argument list to be passed to a new thread or NULL.

security

Pointer to a SECURITY_ATTRIBUTES structure that determines whetherthe returned handle can be inherited by child processes. If NULL, the handlecannot be inherited. Must be NULL for Windows 95 applications.

initflag

Initial state of a new thread (0 forrunning or CREATE_SUSPENDED for suspended); useResumeThread to execute the thread.

thrdaddr

Points to a 32-bit variable that receives the thread identifier.Might be NULL, in which case it is not used.

Return Value

If successful, each of these functions returns a handle to thenewly created thread; however, if the newly created thread exits too quickly, _beginthread might not return a valid handle (see thediscussion in the Remarks section). _beginthread returns -1L on an error, in which case errno is set to EAGAIN if thereare too many threads, to EINVAL if the argument isinvalid or the stack size is incorrect, or to EACCESin the case of insufficient resources (such as memory). _beginthreadexreturns 0 on an error, in which case errno and _doserrno are set.

If startaddress is NULL, the invalid parameter handler is invoked, asdescribed in Parameter Validation. If execution is allowed to continue, these functions set errno to EINVAL and return -1.

For more information about these and other return codes, see_doserrno, errno, _sys_errlist, and _sys_nerr.

For more information about uintptr_t,see Standard Types.

Remarks

The _beginthread function creates athread that begins execution of a routine at start_address.The routine at start_address must use the __cdecl calling convention and should have no returnvalue. When the thread returns from that routine, it is terminatedautomatically. For more information about threads, see Multithreading.

_beginthreadex resembles the Win32 CreateThread API more closely than _beginthread does. _beginthreadexdiffers from _beginthread in the following ways:

·        _beginthreadex has three additional parameters: initflag,security, and threadaddr.The new thread can be created in a suspended state, with a specified security(Windows NT only), and can be accessed using thrdaddr,which is the thread identifier.

·        The routine at start_address passed to _beginthreadexmust use the __stdcall calling convention and mustreturn a thread exit code.

·        _beginthreadex returns 0 on failure, rather than -1L.

·        A thread created with _beginthreadex is terminated by a call to _endthreadex.

The _beginthreadex function gives you morecontrol over how the thread is created than _beginthreaddoes. The _endthreadex function is also moreflexible. For example, with _beginthreadex, you canuse security information, set the initial state of the thread (running orsuspended), and get the thread identifier of the newly created thread. You arealso able to use the thread handle returned by _beginthreadexwith the synchronization APIs, which you cannot do with _beginthread.

It is safer to use _beginthreadex than _beginthread. If the thread generated by _beginthread exits quickly, the handle returned to thecaller of _beginthread might be invalid or, worse,point to another thread. However, the handle returned by _beginthreadexhas to be closed by the caller of _beginthreadex, soit is guaranteed to be a valid handle if _beginthreadexdid not return an error.

You can call _endthread or _endthreadex explicitly to terminate athread; however, _endthread or _endthreadexis called automatically when the thread returns from the routine passed as aparameter. Terminating a thread with a call to endthreador _endthreadex helps to ensure proper recovery ofresources allocated for the thread.

_endthread automatically closes the thread handle (whereas _endthreadex does not). Therefore, when using _beginthread and _endthread, donot explicitly close the thread handle by calling the Win32 CloseHandle API.This behavior differs from the Win32 ExitThread API.

Note:

For an executable file linked with Libcmt.lib , do not call the Win32 ExitThread API; this prevents the run-time system from reclaiming allocated resources. _endthread and _endthreadex reclaim allocated thread resources and then call ExitThread.

The operating system handles the allocation of the stack wheneither _beginthread or _beginthreadexis called; you do not need to pass the address of the thread stack to either ofthese functions. In addition, the stack_sizeargument can be 0, in which case the operating system uses the same value asthe stack specified for the main thread.

arglist is a parameter to be passed to the newly created thread.Typically it is the address of a data item, such as a character string. arglist can be NULL if it isnot needed, but _beginthread and _beginthreadex must be provided with some value to pass tothe new thread. All threads are terminated if any thread calls abort, exit, _exit, or ExitProcess.

The locale of the new thread is inherited from its parent thread.If per thread locale is enabled by a call to _configthreadlocale (eitherglobally or for new threads only), the thread can change its localeindependently from its parent by calling setlocaleor _wsetlocale. For more information, see Locale.

For mixed and pure code, _beginthreadand _beginthreadex both have two overloads, onetaking a native calling-convention function pointer, the other taking a __clrcall function pointer. The first overload is notapplication domain-safe and never will be. If you are writing mixed or purecode you must ensure that the new thread enters the correct application domainbefore it accesses managed resources. You can do this, for example, by usingcall_in_appdomain Function. The second overload is application domain-safe; thenewly created thread will always end up in the application domain of the callerof _beginthread or _beginthreadex.

Requirements

Routine

Required header

_beginthread

<process.h>

_beginthreadex

<process.h>

For more compatibility information, see Compatibility in theIntroduction.

Libraries

Multithreaded versions of the C run-time libraries only.

3.3、线程函数的定义:

_beginthread()和_beginthreadex()的线程执行函数的定义是不一样的。

对于_beginthread()创建的线程,其线程函数定义为:

void ThreadPro(void * pArguments );

对于_beginthreadex()创建的线程,其线程函数定义为:

unsigned __stdcallThreadFunc( void* pArguments )

4、注意事项:

(1)两者创建线程函数方式不同,_beginthreadex()的线程函数必须使用__stdcall调用方式,而且必须返回一个unsigned型的退出码。

(2)_beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1,这一点在检测返回结果时必须注意。

(3)如果是调用_beginthread()创建线程,并相应地调用_endthread()结束线程时,系统将自动关闭线程句柄。而调用_beginthreadex()创建线程,并相应地调用_endthreadex()结束线程时,系统不能自动关闭线程句柄。

(4)由于_beginthread()创建线程参数比较简单,不能控制线程的初始启动状态,且不返回创建的线程句柄,也不能调用

WaitForSingleObject()/WaitForMultipleObjects()函数。所以一般不常用,而_beginthreadex()与CreatThread()函数比较相似。能方便控制线程。

5、多线程实例:

5.1、实例1:

实例说明:该实例主要使用_beginthread()函数,创建尽可能多的线程,知道系统不能再创建为止。注意实时数据的显示和参数的传递,如果想传递多个参数,则可以使用结构体。

主要函数如下:

//在.h文件中:

#define WM_ADD WM_USER+10//定义消息

void ThreadFunc1(void *pArg);//线程函数1:开始创建

void ThreadFunc2(void *pArg);//线程函数2:

LRESULT OnMyMsg(WPARAM, LPARAM);//消息函数

//在.cpp文件中:

//全局变量,一般在cpp中定义,不要在.h定义,否则编译出错:变量重复定义

bool g_bRun = false;

long g_nCount = 0;

//开始按钮函数

void C_beginthreadDlg::OnBnClickedButtonRun()

{

// TODO: 在此添加控件通知处理程序代码

//创建线程:主要是用来创建执行创建线程池的线程

if (_beginthread(ThreadFunc1,0,&m_hWnd) != -1 )

{//成功

GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(FALSE);

}

}

//创建线程池的线程函数

void ThreadFunc1(void *pArg)

{

HWND *hWnd = (HWND*)pArg;//得到窗口句柄

g_nCount= 0;//初始化变量

g_bRun = true;

while (g_bRun)

{//开始创建尽可能多的线程

if (_beginthread(ThreadFunc2,0,hWnd) == -1)

{//失败则结束

g_bRun= false;

break;

}

}

//退出

::SendMessage(*hWnd,WM_ADD,1,0);

}

//线程函数

void ThreadFunc2(void *pArg)

{

HWND *hWnd = (HWND*)pArg;

g_nCount++;

::SendMessage(*hWnd,WM_ADD,0,g_nCount);//显示个数

while(g_bRun)

{

Sleep(1000);

}

}

// WM_ADD消息处理函数

LRESULT C_beginthreadDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)

{

if (wParam == 1)

{//结束

GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(TRUE);

}

else

{//显示更新

m_nCount= g_nCount;

UpdateData(FALSE);

}

return 0;

}

运行效果:

图2:实例1演示

工程源码下载地址:

http://download.csdn.net/detail/cbnotes/4902781

欢迎大家修改和指正。

注意事项:

(1)由于_beginthread()创建的线程结束后自动关闭线程句柄,所以不能使用WaitForSingleObject()/WaitForMultipleObjects()函数来同步。

(2)上面的程序中,先是创建了线程1(ThreadFunc1),再用该线程1来创建众多线程,而不是直接在OnBnClickedButtonRun()函数中创建众多线程,大家知道这是为什么吗?主要是为了能够实时显示创建的线程数目。如果直接在OnBnClickedButtonRun()函数中创建众多线程,主线程将一直处于while循环中,而不能及时处理消息,所以就不能实时显示创建的线程数目(不信的话大家可以试试)。

(3)注意线程函数参数的传递,和消息的发送。如果要传递多个参数,可以使用结构体传递。

5.2、实例2:

实例说明:该实例主要使用_beginthreadex()函数,多人合作(5人)共同完成一项任务,即平时大家说的分工合作,齐头并进。

主要函数如下:

//.h文件中

//声明线程处理函数

unsigned __stdcall ThreadFunc1( void*pArguments );//合作工作线程函数

unsigned __stdcall ThreadFunc2( void*pArguments );//检测工作完成线程函数

//为了传递多个参数,我采用结构体

struct threadInfo

{

HWND hWnd;       //窗口句柄

int  nOffset;    //偏移量

int  nWidth;     //宽度

};

struct threadInfo2

{

HWND   hWnd;           //窗口句柄

HANDLE *phHandle;     //线程句柄指针

};

// 实现

protected:

threadInfoInfo[5];

HANDLE m_hThead[5];    //用于存储线程句柄

HANDLE hThead;     //用于存储线程句柄

unsigned  m_dwThreadID[5];//用于存储线程的ID

threadInfo2Info2;

//在.cpp文件中

//开始按钮函数

void C_beginthreadexDlg::OnBnClickedButtonRun()

{

// TODO: 在此添加控件通知处理程序代码

//使能开始按钮:无效

GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(FALSE);

CDC *dc = GetDC();

CRect rt;

GetClientRect(rt);

dc->FillSolidRect(0,0,rt.Width(),200,RGB(240,240,240));//刷新背景

ReleaseDC(dc);

int nWidth = rt.Width()/5;

//初始化线程的参数

Info[0].hWnd = Info[1].hWnd = Info[2].hWnd = Info[3].hWnd = Info[4].hWnd = GetSafeHwnd();

Info[0].nOffset = 0;Info[1].nOffset = nWidth;Info[2].nOffset= 2*nWidth;Info[3].nOffset = 3*nWidth;Info[4].nOffset= 4*nWidth;

Info[0].nWidth = Info[1].nWidth = Info[2].nWidth = Info[3].nWidth = Info[4].nWidth = nWidth;

//创建个模拟线程

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

{

m_hThead[i] = (HANDLE)_beginthreadex(NULL,0,ThreadFunc1,&Info[i],CREATE_SUSPENDED,&m_dwThreadID[i]);

}

GetDlgItem(IDC_STATIC1)->SetWindowText("进行中...");

//开始运行

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

{

ResumeThread(m_hThead[i]);

}

//开始运行监测结果线程

Info2.hWnd = m_hWnd;

Info2.phHandle = m_hThead;

hThead =(HANDLE)_beginthreadex(NULL,0,ThreadFunc2,&Info2,0,NULL);

}

//合作工作线程函数

unsigned __stdcall ThreadFunc1( void*pArguments )

{

threadInfo*info = (threadInfo*)pArguments;

CDC *dc = CWnd::FromHandle(info->hWnd)->GetDC();

for (int i=info->nOffset;i<info->nOffset+info->nWidth;i++)

{

for (int j =0 ;j<200;j++)

{

dc->SetPixel(i,j,RGB(0,0,0));

}

}

DeleteObject(dc);

return 0;

}

//等待所有的工作结束

unsigned __stdcall ThreadFunc2( void*pArguments )

{

threadInfo2*info = (threadInfo2*)pArguments;

//等待个线程中的一个完成

DWORD dwRet = WaitForMultipleObjects(5,info->phHandle,TRUE,INFINITE);

if (dwRet == WAIT_FAILED)

{//出错啦

::SendMessage(info->hWnd,WM_GAMEOVER,1,0);

return 0;

}

//完成结束

::SendMessage(info->hWnd,WM_GAMEOVER,0,0);

return 0;

}

//消息处理函数

LRESULT C_beginthreadexDlg::OnGameOver(WPARAMwParam, LPARAMlParam)

{

if (wParam ==1)

{//出错

GetDlgItem(IDC_STATIC1)->SetWindowText("出错啦!");

}

else

{//成功

//显示结果

GetDlgItem(IDC_STATIC1)->SetWindowText("任务完成!");

//闪动显示

CDC *dc = GetDC();

CRect rt;

GetClientRect(rt);

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

{

dc->Draw3dRect(0,0,rt.Width(),200,RGB(240,240,240),RGB(240,240,240));//刷新背景

dc->Draw3dRect(1,1,rt.Width()-1,199,RGB(240,240,240),RGB(240,240,240));//刷新背景

Sleep(100);

dc->Draw3dRect(0,0,rt.Width(),200,RGB(255,0,0),RGB(255,0,0));//刷新背景

dc->Draw3dRect(1,1,rt.Width()-1,199,RGB(255,0,0),RGB(255,0,0));//刷新背景

Sleep(100);

}

ReleaseDC(dc);

}

//关闭句柄

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

{

CloseHandle(m_hThead[i]);

}

CloseHandle(hThead);

//使能开始按钮,以便可以开始下一次比赛

GetDlgItem(IDC_BUTTON_RUN)->EnableWindow(TRUE);

return 0;

}

运行效果:

工程源码下载地址:

http://download.csdn.net/detail/cbnotes/4905693

欢迎大家修改和指正。

注意事项:

(1)由于_beginthreadex()创建的线程结束后自动调用_endthreadex()函数,但是不会关闭线程句柄,所以必须手动关闭句柄,但能使用同步函数,如:

WaitForSingleObject()/WaitForMultipleObjects()函数来同步。

(2)通过该实例还可以稍加改进,就可以统计5个线程的各自的工作时间,并排序。欢迎大家多多练习。

(3)由_beginthreadex()函数创建线程,能灵和控制和管理线程,所以推荐使用该方式。

=================================================转载请标明出处,谢谢.http://blog.csdn.net/cbNotes

Z

VC.【转】采用_beginthread/_beginthreadex函数创建多线程的更多相关文章

  1. java创建多线程(转载)

    转载自:Java创建线程的两个方法 Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对 ...

  2. Python并发编程之创建多线程的几种方法(二)

    大家好,并发编程 进入第二篇. 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础.学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章. 本文目录 学会使用函数创建多 ...

  3. _beginThreadex创建多线程解读【转】

    _beginThreadex创建多线程解读 一.需要的头文件支持 #include <process.h>         // for _beginthread() 需要的设置:Proj ...

  4. _beginThreadex创建多线程解读

    _beginThreadex创建多线程解读 一.须要的头文件支持 #include <process.h>         // for _beginthread() 须要的设置:Proj ...

  5. 使用_beginThreadex创建多线程(C语言版多线程)

    _beginThreadex创建多线程解读 一.需要的头文件支持 #include <process.h>         // for _beginthread() 需要的设置:Proj ...

  6. _beginthreadex创建多线程详解

    一.需要的头文件支持 #include <process.h>         // for _beginthread() 需要的设置:ProjectSetting-->C/C++- ...

  7. 转:MFC中创建多线程

    MFC中创建多线程   MFC的多线程函数必须声明为静态的或者是全局函数(不同的在于全局函数不能访问类的私有静态成员,而静态类函数可以):但这样的线程函数只能访问静态的成员变量,要实现访问类的其他成员 ...

  8. 在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除。

    在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除. 作者:邵盛松 2009-09-05 前言 1关于的BLOB(Binary)数据的存储和读取功能主要参考了MSDN上的一篇& ...

  9. 使用CreateThread函数和_beginThreadex函数的注意事项

    作者:朱金灿 来源:http://blog.csdn.net/clever101 使用CreateThread函数创建线程时,类或结构体的变量作为CreateThread函数传递给线程函数的参数需要避 ...

随机推荐

  1. close yield

    close的方法主要是关闭子生成器,需要注意的有4点: 1.如果生成器close后,还继续next,会报错StopIteration   [图片]     2.如果我捕获了异常,将GeneratorE ...

  2. diff 命令实用

    1.概述 本文将要讨论的是diff命令,diff用来比较两个文件.当然文件比较的工具很多,windows系统下面就有不错的工具可以使用,例如常用的Beyond Compare,WinMerge都是图形 ...

  3. java Condition条件变量的通俗易懂解释、基本使用及注意点

    最近在看pthread方面的书,看到条件变量一节的时候,回忆了下java中条件变量的使用方式. java中条件变量都实现了java.util.concurrent.locks.Condition接口, ...

  4. Ubuntu 18.04.1更改屏幕分辨率

  5. hdu 1151 Air Raid - 二分匹配

    Consider a town where all the streets are one-way and each street leads from one intersection to ano ...

  6. ubuntu 容器安装ping ifconfig ip命令

    进入容器测试ifconfig  ping 没有-------->>很尴尬 apt-get install net-tools ###   ifconfig apt-get install ...

  7. wait()和notify()的理解与使用

    void notify() Wakes up a single thread that is waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的单个线程 ...

  8. How to use Junit Listener

    JUnit Listeners If you want to do some operations when your tests are started, passed, finished, fai ...

  9. 【做题】51NOD1753 相似子串——哈希

    题意:两个字符串相似定义为: 1.两个字符串长度相等 2.两个字符串对应位置上至多有一个位置所对应的字符不相同 给定一个字符串\(s\),\(T\)次询问两个子串在给定的规则下是否相似.给定的规则指每 ...

  10. oracle单行函数 之 字符函数

    Upper(字符串 / 列):将输入的字符串变成大写 Lower(字符串 / 列):将输入的字符串变成小写 Initcap(字符串 / 列):开头首字母大写 Length(字符串 / 列):字符串长度 ...