现在在学习多线程,顺便将搜集到的资料整理下来以供参考和查询。
首先在开始多线程学习的时候遇到的首要问题便是多线程的创建,在查阅资料后有CreateThread和_beginthreadex两种方法,可能不止这两种,以后学习到了再补充。
----------------------------------------------20160914更新-------------------------------------------------
在创建新的线程时一定要使用_beginthreadex函数而不要使用CreateThread函数,因为CreateThread函数对系统中的全局变量没有保护,所以多个线程环境下容易出现系统的全局变量的值被覆盖的情况,而_beginthreadex每个线程都有单独的系统全局变量入errno.
----------------------------------------------------------------------------------------------------------------------------------------------------------
(1)CreateThread
HANDLE
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程的安全属性,NULL则为默认属性
_In_ SIZE_T dwStackSize, //指定线程堆栈大小,NULL则默认堆栈大小
_In_ LPTHREAD_START_ROUTINE lpStartAddress, //线程函数其实地址及执行线程的函数
_In_opt_ __drv_aliasesMem LPVOID lpParameter, //传递给线程函数的参数
_In_ DWORD dwCreationFlags, //指定线程创建后是否立即执行,若0则表示线程立即执行
_Out_opt_ LPDWORD lpThreadId
);
lpThreadAttributes,是一个执行SECURITY_ATTRIBUTES结构的指针,如果需要默认安全属性则传递参数NULL,
如果希望此线程对象句柄可以被子进程继承的话,必须设定一个SECURITY_ATTRIBUTES的机构,将它的bInheritHandle成员初始化为TRUE
如下:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.IpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; //使CreateThread返回的句柄可以被继承
HANDLE handle = CreateThread(&sa,...);
dwCreationFlags,创建标志,如果是0表示线程被创建后立即开始运行,如果指定为CREATE_SUSPENDED标志,表示线程被创建以后处于挂
起状态,直到使用ResumeThread显式启动线程为止。
#include
#include
//子线程函数
DWORD WINAPI ThreadFun(LPVOID pM)
{
printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());
return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForSingleObject(handle, INFINITE);
return 0;
}
其中DWORD:unsigned long; WINAPI:_stdcall; LPVOID:void *
(2)_beginthreadex函数
在C/C++标准运行库里面有许多的全局变量入errno、strerror等,他们可以用来表示线程当前的状态。但是在多线程设计中每个线程必须有唯一的状态,佛则这些变量记录的信息准确度无法保证。如下代码所示
if (system("notepad.exe readme.txt") == -1)
{
switch(errno)
{
....//程序处理错误代码
}
}
加入某个线程A执行上面的代码,在执行system后,还未来得及调用switch()函数,这时线程B同样执行该代码出错,并将错误代号写入全局变量errno中,这样,A一旦开始执行swtich()时,它所访问的代码并不是本身错误代码而是线程B赋予的错误代码。像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函数也会遇到这种由于多个线程访问修改导致的数据覆盖问题。
为了避免此类情况发生,Windows操作系统提供了标准运行库函数 _beginthreadex,让运行期设置了相关变量后再去调用Windows系统提供的CreateThread函数。_beginthreadex的参数与CreateThread函数是对应的,只是参数名和类型完全不同,使用的时候需要进行强制转化。_beginthreadex函数定义如下:
_ACRTIMP uintptr_t __cdecl _beginthreadex(
_In_opt_ void* _Security,
_In_ unsigned _StackSize,
_In_ _beginthreadex_proc_type _StartAddress,
_In_opt_ void* _ArgList,
_In_ unsigned _InitFlag,
_Out_opt_ unsigned* _ThrdAddr
);
#include
#include
#include
//子线程函数
unsigned int __stdcall ThreadFun(PVOID pM)
{
printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
return 0;
}
//主函数,所谓主函数其实就是主线程执行的函数。
int main()
{
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;
}