windows多线程详解

2023-05-16

在一个牛人的博客上看到了这篇文章,所以就转过来了,地址是http://blog.csdn.net/morewindows/article/details/7421759

本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread_beginthreadex到底有什么区别,在实际的编程中到底应该使用CreateThread还是_beginthreadex

 

   使用多线程其实是非常容易的,下面这个程序的主线程会创建了一个子线程并等待其运行完毕,子线程就输出它的线程ID号然后输出一句经典名言——Hello World。整个程序的代码非常简短,只有区区几行。

[cpp] view plain copy print ?
  1. //最简单的创建多线程实例   
  2. #include <stdio.h>   
  3. #include <windows.h>   
  4. //子线程函数   
  5. DWORD WINAPI ThreadFun(LPVOID pM)  
  6. {  
  7.     printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());  
  8.     return 0;  
  9. }  
  10. //主函数,所谓主函数其实就是主线程执行的函数。   
  11. int main()  
  12. {  
  13.     printf("     最简单的创建多线程实例\n");  
  14.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  15.   
  16.     HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);  
  17.     WaitForSingleObject(handle, INFINITE);  
  18.     return 0;  
  19. }  
//最简单的创建多线程实例
#include <stdio.h>
#include <windows.h>
//子线程函数
DWORD WINAPI ThreadFun(LPVOID pM)
{
	printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());
	return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
	printf("     最简单的创建多线程实例\n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");

	HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
	WaitForSingleObject(handle, INFINITE);
	return 0;
}

运行结果如下所示:

下面来细讲下代码中的一些函数

第一个 CreateThread

函数功能:创建线程

函数原型:

HANDLEWINAPICreateThread(

 LPSECURITY_ATTRIBUTESlpThreadAttributes,

 SIZE_TdwStackSize,

  LPTHREAD_START_ROUTINElpStartAddress,

 LPVOIDlpParameter,

 DWORDdwCreationFlags,

 LPDWORDlpThreadId

);

函数说明:

第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。

第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

第四个参数是传给线程函数的参数。

第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()

第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

函数返回值:

成功返回新线程的句柄,失败返回NULL 

 

第二个 WaitForSingleObject

函数功能:等待函数使线程进入等待状态,直到指定的内核对象被触发。

函数原形:

DWORDWINAPIWaitForSingleObject(

 HANDLEhHandle,

 DWORDdwMilliseconds

);

函数说明:

第一个参数为要等待的内核对象。

第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。

因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。

函数返回值:

在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

 

CreateThread()函数是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex(),在很多书上(包括《Windows核心编程》)提到过尽量使用_beginthreadex()来代替使用CreateThread(),这是为什么了?下面就来探索与发现它们的区别吧。

 

       首先要从标准C运行库与多线程的矛盾说起,标准C运行库在1970年被实现了,由于当时没任何一个操作系统提供对多线程的支持。因此编写标准C运行库的程序员根本没考虑多线程程序使用标准C运行库的情况。比如标准C运行库的全局变量errno。很多运行库中的函数在出错时会将错误代号赋值给这个全局变量,这样可以方便调试。但如果有这样的一个代码片段:

[cpp] view plain copy print ?
  1. if (system("notepad.exe readme.txt") == -1)  
  2. {  
  3.     switch(errno)  
  4.     {  
  5.         ...//错误处理代码   
  6.     }  
  7. }  
if (system("notepad.exe readme.txt") == -1)
{
	switch(errno)
	{
		...//错误处理代码
	}
}

假设某个线程A在执行上面的代码,该线程在调用system()之后且尚未调用switch()语句时另外一个线程B启动了,这个线程B也调用了标准C运行库的函数,不幸的是这个函数执行出错了并将错误代号写入全局变量errno中。这样线程A一旦开始执行switch()语句时,它将访问一个被B线程改动了的errno。这种情况必须要加以避免!因为不单单是这一个变量会出问题,其它像strerror()strtok()tmpnam()gmtime()asctime()等函数也会遇到这种由多个线程访问修改导致的数据覆盖问题。

 

为了解决这个问题,Windows操作系统提供了这样的一种解决方案——每个线程都将拥有自己专用的一块内存区域来供标准C运行库中所有有需要的函数使用。而且这块内存区域的创建就是由C/C++运行库函数_beginthreadex()来负责的。下面列出_beginthreadex()函数的源代码(我在这份代码中增加了一些注释)以便读者更好的理解_beginthreadex()函数与CreateThread()函数的区别。

[cpp] view plain copy print ?
  1. //_beginthreadex源码整理By MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. _MCRTIMP uintptr_t __cdecl _beginthreadex(  
  3.     void *security,  
  4.     unsigned stacksize,  
  5.     unsigned (__CLR_OR_STD_CALL * initialcode) (void *),  
  6.     void * argument,  
  7.     unsigned createflag,  
  8.     unsigned *thrdaddr  
  9. )  
  10. {  
  11.     _ptiddata ptd;          //pointer to per-thread data 见注1  
  12.     uintptr_t thdl;         //thread handle 线程句柄  
  13.     unsigned long err = 0L; //Return from GetLastError()  
  14.     unsigned dummyid;    //dummy returned thread ID 线程ID号  
  15.       
  16.     // validation section 检查initialcode是否为NULL  
  17.     _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);  
  18.   
  19.     //Initialize FlsGetValue function pointer  
  20.     __set_flsgetvalue();  
  21.       
  22.     //Allocate and initialize a per-thread data structure for the to-be-created thread.  
  23.     //相当于new一个_tiddata结构,并赋给_ptiddata指针。  
  24.     if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )  
  25.         goto error_return;  
  26.   
  27.     // Initialize the per-thread data  
  28.     //初始化线程的_tiddata块即CRT数据区域 见注2   
  29.     _initptd(ptd, _getptd()->ptlocinfo);  
  30.       
  31.     //设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。  
  32.     ptd->_initaddr = (void *) initialcode; //线程函数地址  
  33.     ptd->_initarg = argument;              //传入的线程参数  
  34.     ptd->_thandle = (uintptr_t)(-1);  
  35.       
  36. #if defined (_M_CEE) || defined (MRTDLL)  
  37.     if(!_getdomain(&(ptd->__initDomain))) //见注3  
  38.     {  
  39.         goto error_return;  
  40.     }  
  41. #endif  // defined (_M_CEE) || defined (MRTDLL)  
  42.       
  43.     // Make sure non-NULL thrdaddr is passed to CreateThread  
  44.     if ( thrdaddr == NULL )//判断是否需要返回线程ID号  
  45.         thrdaddr = &dummyid;  
  46.   
  47.     // Create the new thread using the parameters supplied by the caller.  
  48.     //_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程  
  49.     if ( (thdl = (uintptr_t)CreateThread(  
  50.                     (LPSECURITY_ATTRIBUTES)security,  
  51.                     stacksize,  
  52.                     _threadstartex,  
  53.                     (LPVOID)ptd,  
  54.                     createflag,  
  55.                     (LPDWORD)thrdaddr))  
  56.         == (uintptr_t)0 )  
  57.     {  
  58.         err = GetLastError();  
  59.         goto error_return;  
  60.     }  
  61.   
  62.     //Good return   
  63.     return(thdl); //线程创建成功,返回新线程的句柄.  
  64.       
  65.     //Error return   
  66. error_return:  
  67.     //Either ptd is NULL, or it points to the no-longer-necessary block  
  68.     //calloc-ed for the _tiddata struct which should now be freed up.  
  69.     //回收由_calloc_crt()申请的_tiddata块  
  70.     _free_crt(ptd);  
  71.     // Map the error, if necessary.  
  72.     // Note: this routine returns 0 for failure, just like the Win32  
  73.     // API CreateThread, but _beginthread() returns -1 for failure.  
  74.     //校正错误代号(可以调用GetLastError()得到错误代号)   
  75.     if ( err != 0L )  
  76.         _dosmaperr(err);  
  77.     return( (uintptr_t)0 ); //返回值为NULL的效句柄  
  78. }  
//_beginthreadex源码整理By MoreWindows( http://blog.csdn.net/MoreWindows )
_MCRTIMP uintptr_t __cdecl _beginthreadex(
	void *security,
	unsigned stacksize,
	unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
	void * argument,
	unsigned createflag,
	unsigned *thrdaddr
)
{
	_ptiddata ptd;          //pointer to per-thread data 见注1
	uintptr_t thdl;         //thread handle 线程句柄
	unsigned long err = 0L; //Return from GetLastError()
	unsigned dummyid;    //dummy returned thread ID 线程ID号
	
	// validation section 检查initialcode是否为NULL
	_VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);

	//Initialize FlsGetValue function pointer
	__set_flsgetvalue();
	
	//Allocate and initialize a per-thread data structure for the to-be-created thread.
	//相当于new一个_tiddata结构,并赋给_ptiddata指针。
	if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
		goto error_return;

	// Initialize the per-thread data
	//初始化线程的_tiddata块即CRT数据区域 见注2
	_initptd(ptd, _getptd()->ptlocinfo);
	
	//设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。
	ptd->_initaddr = (void *) initialcode; //线程函数地址
	ptd->_initarg = argument;              //传入的线程参数
	ptd->_thandle = (uintptr_t)(-1);
	
#if defined (_M_CEE) || defined (MRTDLL)
	if(!_getdomain(&(ptd->__initDomain))) //见注3
	{
		goto error_return;
	}
#endif  // defined (_M_CEE) || defined (MRTDLL)
	
	// Make sure non-NULL thrdaddr is passed to CreateThread
	if ( thrdaddr == NULL )//判断是否需要返回线程ID号
		thrdaddr = &dummyid;

	// Create the new thread using the parameters supplied by the caller.
	//_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程
	if ( (thdl = (uintptr_t)CreateThread(
					(LPSECURITY_ATTRIBUTES)security,
					stacksize,
					_threadstartex,
					(LPVOID)ptd,
					createflag,
					(LPDWORD)thrdaddr))
		== (uintptr_t)0 )
	{
		err = GetLastError();
		goto error_return;
	}

	//Good return
	return(thdl); //线程创建成功,返回新线程的句柄.
	
	//Error return
error_return:
	//Either ptd is NULL, or it points to the no-longer-necessary block
	//calloc-ed for the _tiddata struct which should now be freed up.
	//回收由_calloc_crt()申请的_tiddata块
	_free_crt(ptd);
	// Map the error, if necessary.
	// Note: this routine returns 0 for failure, just like the Win32
	// API CreateThread, but _beginthread() returns -1 for failure.
	//校正错误代号(可以调用GetLastError()得到错误代号)
	if ( err != 0L )
		_dosmaperr(err);
	return( (uintptr_t)0 ); //返回值为NULL的效句柄
}

讲解下部分代码:

1_ptiddataptd;中的_ptiddata是个结构体指针。在mtdll.h文件被定义:

      typedefstruct_tiddata *_ptiddata

微软对它的注释为Structure for each thread's data这是一个非常大的结构体,有很多成员。本文由于篇幅所限就不列出来了。

 

2_initptd(ptd,_getptd()->ptlocinfo);微软对这一句代码中的getptd()的说明为:

     /* return address of per-thread CRT data */

     _ptiddata __cdecl_getptd(void);

_initptd()说明如下:

     /* initialize a per-thread CRT data block */

     void__cdecl_initptd(_Inout_ _ptiddata_Ptd,_In_opt_ pthreadlocinfo_Locale);

注释中的CRTC Runtime Library)即标准C运行库。

 

3if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函数代码可以在thread.c文件中找到,其主要功能是初始化COM环境。

 

由上面的源代码可知,_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()相信阅读到这里时,你会对这句简短的话有个非常深刻的印象,如果有面试官问起,你也可以流畅准确的回答了^_^

 

接下来,类似于上面的程序用CreateThread()创建输出“Hello World”的子线程,下面使用_beginthreadex()来创建多个子线程:

[cpp] view plain copy print ?
  1. //创建多子个线程实例   
  2. #include <stdio.h>   
  3. #include <process.h>   
  4. #include <windows.h>   
  5. //子线程函数   
  6. unsigned int __stdcall ThreadFun(PVOID pM)  
  7. {  
  8.     printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());  
  9.     return 0;  
  10. }  
  11. //主函数,所谓主函数其实就是主线程执行的函数。   
  12. int main()  
  13. {  
  14.     printf("     创建多个子线程实例 \n");  
  15.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  16.       
  17.     const int THREAD_NUM = 5;  
  18.     HANDLE handle[THREAD_NUM];  
  19.     for (int i = 0; i < THREAD_NUM; i++)  
  20.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  
  21.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  22.     return 0;  
  23. }  
//创建多子个线程实例
#include <stdio.h>
#include <process.h>
#include <windows.h>
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
	printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
	return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
	printf("     创建多个子线程实例 \n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
	
	const int THREAD_NUM = 5;
	HANDLE handle[THREAD_NUM];
	for (int i = 0; i < THREAD_NUM; i++)
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
	return 0;
}

运行结果如下:

图中每个子线程说的都是同一句话,不太好看。能不能来一个线程报数功能,即第一个子线程输出1,第二个子线程输出2,第三个子线程输出3,……。要实现这个功能似乎非常简单——每个子线程对一个全局变量进行递增并输出就可以了。代码如下:

[cpp] view plain copy print ?
  1. //子线程报数   
  2. #include <stdio.h>   
  3. #include <process.h>   
  4. #include <windows.h>   
  5. int g_nCount;  
  6. //子线程函数   
  7. unsigned int __stdcall ThreadFun(PVOID pM)  
  8. {  
  9.     g_nCount++;  
  10.     printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);  
  11.     return 0;  
  12. }  
  13. //主函数,所谓主函数其实就是主线程执行的函数。   
  14. int main()  
  15. {  
  16.     printf("     子线程报数 \n");  
  17.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  18.       
  19.     const int THREAD_NUM = 10;  
  20.     HANDLE handle[THREAD_NUM];  
  21.   
  22.     g_nCount = 0;  
  23.     for (int i = 0; i < THREAD_NUM; i++)  
  24.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);  
  25.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  26.     return 0;  
  27. }  
//子线程报数
#include <stdio.h>
#include <process.h>
#include <windows.h>
int g_nCount;
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
	g_nCount++;
	printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);
	return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
	printf("     子线程报数 \n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
	
	const int THREAD_NUM = 10;
	HANDLE handle[THREAD_NUM];

	g_nCount = 0;
	for (int i = 0; i < THREAD_NUM; i++)
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
	WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
	return 0;
}

对一次运行结果截图如下:

显示结果从1数到10,看起来好象没有问题。

       答案是不对的,虽然这种做法在逻辑上是正确的,但在多线程环境下这样做是会产生严重的问题,下一篇《秒杀多线程第三篇 原子操作 Interlocked系列函数》将为你演示错误的结果(可能非常出人意料)并解释产生这个结果的详细原因。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

windows多线程详解 的相关文章

随机推荐

  • MapReduce原理及编程实现

    文章目录 MapReduce原理及编程实现MapReduce基本概念MapReduce执行过程Mapper阶段Reducer阶段Combiner类Partitioner类 MapReduce实现WordCountKey amp Value类
  • 2020-10-20 学习日志(Crazepony控制环)

    2020年10月20日 学习任务 xff1a 完成Crazepony控制环的理解 之前是通过姿态解算获得了 四元数 旋转矩阵 欧拉角 CtrlAttiRate void CtrlAttiRate void float yawRateTarg
  • STL学习笔记之迭代器--iterator

    STL设计的精髓在于 xff0c 把容器 xff08 Containers xff09 和算法 xff08 Algorithms xff09 分开 xff0c 彼此独立设计 xff0c 最后再用迭代器 xff08 Iterator xff0
  • 提升工作效率之PCB设计软件“立创EDA”

    文章目录 前言一 立创EDA二 PCB生产三 团队功能总结 前言 由于工作需要设计一款硬件调试小工具 xff0c 考虑到器件采购和PCB制版都在立创商城上进行 xff0c 索性就试用立创EDA进行PCB设计 结论在前 xff1a 立创线上E
  • nvidia显卡,驱动以及cuda版本对应查询

    实验室新买了一块rtx 2080和titan rtx xff0c 需要分别配置驱动和cuda xff0c 但是一直也找不到显卡和cuda的官方对照表 xff0c 每次都是百度 谷歌 必应 xff0c 参考别人安装之旅 今天突然发现了驱动和c
  • LoRa 信噪比和接收灵敏度

    文章目录 前言一 信噪比极限 xff08 SNR LIMIT xff09 二 接收灵敏度 前言 介绍信噪比极限和如何计算接收灵敏度 参考资料 xff1a LoRa信噪比和接收灵敏度 一 信噪比极限 xff08 SNR LIMIT xff09
  • C在字符串后面加/0和0

    使用复制字符串时 xff0c 经常会遇到字符串后面跟着一大堆莫名其妙的字符串 xff0c 例如屯屯屯 之类的东西 xff0c 这是因为在使用字符串时没有在字符串结尾加 0或0 通常分配一块内存到堆上或栈上时 xff0c 内存区域可能会有之前
  • 基于k8s+prometheus实现双vip可监控Web高可用集群

    目录 一 规划整个项目的拓扑结构和项目的思维导图 二 修改好各个主机的主机名 xff0c 并配置好每台机器的ip地址 网关和dns等 2 1修改每个主机的ip地址和主机名 2 2 关闭firewalld和selinux 三 使用k8s实现W
  • PX4源码开发人员文档(一)——软件架构

    软件架构 PX4 在广播消息网络内 xff0c 按照一组节点 xff08 nodes xff09 的形式进行组织 xff0c 网络之间使用像如 姿态 和 位置 之类的语义通道来传递系统状态 软件的堆栈结构主要分为四层 应用程序接口 提供给a
  • ardupilot线程理解

    对于apm和pixhawk一直存在疑惑 xff0c 到现在还不是特别清楚 今天在http dev ardupilot com 看到下面的说明 xff0c 感觉很有用 xff0c 对于整体理解amp代码很有帮助 xff0c 所以记下来 转载请
  • Pixhawk源码笔记三:串行接口UART和Console

    这里 xff0c 我们对 APM UART Console 接口进行讲解 如有问题 xff0c 可以交流30175224 64 qq com 新浪 64 WalkAnt xff0c 转载本博客文章 xff0c 请注明出处 xff0c 以便更
  • C/C++中二维数组和指针关系分析

    在C c 43 43 中 xff0c 数组和指针有着密切的关系 xff0c 有很多地方说数组就是指针式错误的一种说法 这两者是不同的数据结构 其实 xff0c 在C c 43 43 中没有所谓的二维数组 xff0c 书面表达就是数组的数组
  • 四叉树空间索引原理及其实现

    今天依然在放假中 xff0c 在此将以前在学校写的四叉树的东西拿出来和大家分享 四叉树索引的基本思想是将地理空间递归划分为不同层次的树结构 它将已知范围的空间等分成四个相等的子空间 xff0c 如此递归下去 xff0c 直至树的层次达到一定
  • DirectXShaderCompiler mac编译

    Directxshader compiler mac编译 1 前置条件 Please make sure you have the following resources before building GitPython Version
  • intel -tbb 源码cmake构建

    cmake minimum required VERSION 3 0 0 FATAL ERROR set CMAKE CXX STANDARD 17 project tbb CXX add library tbb SHARED void c
  • 如何修改数据库密码

    多可文档管理系统是自带数据库的 xff0c 就是你在安装多可文档管理系统的同时 xff0c 数据库就已经自动安装上了 这个数据库有个默认密码 xff0c 为了数据库里的数据安全 xff0c 建议你安装完多可后 xff0c 就立刻修改数据库的
  • iOS编译openmp

    1 下载openmp源码 https github com llvm llvm project releases download llvmorg 14 0 6 openmp 14 0 6 src tar xz 2 下载ios toolch
  • 我的2013-从GIS学生到GIS职业人的飞跃

    我的 2013 从 GIS 学生GIS 职业人的飞跃 前言 xff1a 从末日中度过了 2012 年 xff0c 我们伟大的人类把这个世界末日的谎言给揭穿了 xff0c 但是不知不觉中 xff0c 2013 年又即将悄悄从我们身边溜走 xf
  • 矩阵的特征值和特征向量的雅克比算法C/C++实现

    矩阵的特征值和特征向量是线性代数以及矩阵论中非常重要的一个概念 在遥感领域也是经常用到 xff0c 比如多光谱以及高光谱图像的主成分分析要求解波段间协方差矩阵或者相关系数矩阵的特征值和特征向量 根据普通线性代数中的概念 xff0c 特征值和
  • windows多线程详解

    在一个牛人的博客上看到了这篇文章 xff0c 所以就转过来了 xff0c 地址是http blog csdn net morewindows article details 7421759 本文将带领你与多线程作第一次亲密接触 xff0c