• Chapter06C/C++运行库


    在上一篇关于线程的讲解中,有提到一般我们都不应该直接调用CreateThread函数去创建新线程,而是调用_beginthreadex函数创建新线程。

    以下是_beginthreadex函数的伪代码:

    uintptr_t __cdecl _beginthreadex (
    	void *psa,
    	unsigned cbStackSize,
    	unsigned (__stdcall * pfnStartAddr) (void *),
    	void * pvParam,
    	unsigned dwCreateFlags,
    	unsigned *pdwThreadID) {
    		_ptiddata ptd; // Pointer to thread's data block
    		uintptr_t thdl; // Thread's handle
    		// Allocate data block for the new thread.
    		if ((ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL)
    			goto error_return;
    		// Initialize the data block.
    		initptd(ptd);
    		// Save the desired thread function and the parameter
    		// we want it to get in the data block.
    		ptd->_initaddr = (void *) pfnStartAddr;
    		ptd->_initarg = pvParam;
    		ptd->_thandle = (uintptr_t)(-1);
    		// Create the new thread.
    		thdl = (uintptr_t) CreateThread((LPSECURITY_ATTRIBUTES)psa, cbStackSize,
    			_threadstartex, (PVOID) ptd, dwCreateFlags, pdwThreadID);
    		if (thdl == 0) {
    			// Thread couldn't be created, cleanup and return failure.
    			goto error_return;
    		}
    		// Thread created OK, return the handle as unsigned long.
    		return(thdl);
    error_return:
    		// Error: data block or thread couldn't be created.
    		// GetLastError() is mapped into errno corresponding values
    		// if something wrong happened in CreateThread.
    		_free_crt(ptd);
    		return((uintptr_t)0L);
    }


    关于_beginthreadex函数我们需要注意:

    • 每个线程从C/C++运行库堆中获取自己的_tiddata内存块。
    • 传递给_beginthreadex的线程函数地址记录在_tiddata内存块中。
    • _beginthreadex函数内部调用了CreateThread函数,因为这是操作系统知道的创建新线程的唯一方法。
    • 当CreateThread函数被调用时,它会被告知要开始执行_threadstartex函数(而不是pfnStartAddr)去开始一个新线程。同时也需要注意,此时传递过去的参数是_tiddata结构体地址而不是pvParam.
    • 如果一切顺利的话,就会像CreateThread函数一样返回线程句柄。如果操作失败,则返回0值。


    ////////////////////////////////////////////////////////////////


    上面提到了重要的_threadstartex函数,下面是它的伪代码:

    static unsigned long WINAPI _threadstartex (void* ptd) {
    	// Note: ptd is the address of this thread's tiddata block.
    	// Associate the tiddata block with this thread so
    	// _getptd() will be able to find it in _callthreadstartex.
    	TlsSetValue(__tlsindex, ptd);
    	// Save this thread ID in the _tiddata block.
    	//Windows via C/C++, Fifth Edition by Jeffrey Richter and Christophe Nasarre
    		((_ptiddata) ptd)->_tid = GetCurrentThreadId();
    	// Initialize floating-point support (code not shown).
    	// call helper function.
    	_callthreadstartex();
    	// We never get here; the thread dies in _callthreadstartex.
    	return(0L);
    }
    static void _callthreadstartex(void) {
    	_ptiddata ptd; /* pointer to thread's _tiddata struct */
    	// get the pointer to thread data from TLS
    	ptd = _getptd();
    	// Wrap desired thread function in SEH frame to
    	// handle run-time errors and signal support.
    	__try {
    		// Call desired thread function, passing it the desired parameter.
    		// Pass thread's exit code value to _endthreadex.
    		_endthreadex(
    			((unsigned (WINAPI *)(void *))(((_ptiddata)ptd)->_initaddr))
    			(((_ptiddata)ptd)->_initarg)) ;
    	}
    	__except(_XcptFilter(GetExceptionCode(), GetExceptionInformation())){
    		// The C run-time's exception handler deals with run-time errors
    		// and signal support; we should never get it here.
    		_exit(GetExceptionCode());
    	}
    }

    关于_threadstartex函数,需要注意:

    • 一个新线程最开始执行RtlUserThreadStart函数,然后跳到_threadstartex。
    • 新线程的_tiddata数据块是_threadstartex函数唯一的参数。
    • TlsSetValue函数是一个将一个数值关联到线程的系统函数。
    • 在无参数的_callthreadstartex函数中,有一个SEH帧,它将预期要执行的线程函数包围起来。这个SEH帧处理许多鱼运行库相关的事情(比如运行错误等)。
    • 接下来就执行预期函数pfnStartAddr,并传递预期参数pvParam。pfnStartAddr和pvParam之前被记录在_tiddata块中。
    • 预期的线程函数返回值就是线程的退出码。值得注意的是:_callthreadstartex不仅仅是返回至_threadstartex然后再返回到RtlUserThreadStart;如果真的这样做,线程会消亡,退出码也能正确设置,但是线程的_tiddata内存块不会被销毁。这样将会导致你的程序出现内存泄漏。为了防止这种情况,就需要调用C/C++运行库函数--_endthreadex。



    ////////////////////////////////////////////////////////////////


    下面就来介绍_endthreadex函数,下面就是其伪代码:

    void __cdecl _endthreadex (unsigned retcode) {
    	_ptiddata ptd; // Pointer to thread's data block
    	// Clean up floating-point support (code not shown).
    	// Get the address of this thread's tiddata block.
    	ptd = _getptd_noexit ();
    	// Free the tiddata block.
    	if (ptd != NULL)
    		_freeptd(ptd);
    	// Terminate the thread.
    	ExitThread(retcode);
    }

    对于_endthreadex函数,我们需要注意的是:

    • 当你的线程函数返回时,_beginthreadex函数会调用_endthreadex函数。
    • C运行库的_getptd_noexit函数内部调用操作系统的TlsGetValue函数,TlsGetValue函数获取调用线程的tiddata内存块地址。
    • 这个_tiddata数据块之后被释放,然后调用操作系统ExitThread函数去真正销毁线程。

    在实际编程过程中,我们也不应该直接调用ExitThread函数去退出一个线程,而是调用_endthreadex函数。原因有两个:

    • 调用ExitThread会杀死线程,而不会让执行的线程函数返回。如果线程函数没有返回,则函数内的C++对象就不会被销毁。
    • 调用ExitThread退出程序后,不会回收_tiddata内存块。这样你的应用程序存在内存泄漏。

  • 相关阅读:
    B Graph(异或MST)
    G. Xor-MST(边权为俩点值的异或值,求MST)
    H Harmony Pairs(找(大小)和(位数和大小)逆序的点对,数位dp)
    hdu6787(根据喜欢程度分配得最大总价值,最大费用最大流)
    Codeforces Global Round 2
    2019西北工业大学程序设计创新实践基地春季选拔赛(重现赛)
    Java EE学习笔记(九)
    Java EE学习笔记(八)
    Codeforces Round #549 (Div. 2)
    Codeforces Round #550 (Div. 3)
  • 原文地址:https://www.cnblogs.com/java20130722/p/3207148.html
Copyright © 2020-2023  润新知