使用多线程非常容易, 以前在linux
上学习的时候没有这么详细的区分, 也没有深入的了解多线程, 在linux
上只要包含头文件pthread
即可, 最近学习windows
上才有了很多区分, 例如C++11
新标准的头文件<thread>
也是可以创建多线程或者windows.h
中的CreateThread
和_beginthreadex
两个都是可以创建多线程的
#include <stdio.h>
#include <windows.h>
/*
之前一直存在疑惑点, 为什么返回值为void函数就出现不能调用, 说函数类型不匹配, 通过别人的一篇文章才知道原因, 然后查看CreateThread源码发现有这么一个宏定义
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
定义的函数指针为: 返回值为DWORD 函数参数为void* 名为函数指针PTHREAD_START_ROUTINE的指针
*/
DWORD WINAPI threadFun(LPVOID PM)
{
printf("%d",GetcurrentThreadId());
return 0;
}
int main()
{
HANDLE handle = CreateThread(NULL,0,threadFun,NULL,0,NULL);
WaitForsingleObject(handle,INFINITE);//INFINITE阻塞等待
CloseHandle(handle);//主进程放弃对线程的控制
return 0;
}
CreateThread()
是微软在Windows API
中提供的线程函数, 该函数会在主函数的进程中创建一个新的线程, 如果创建成果那么函数向线程返回一个句柄, 如果失败则返回NULL, 线程终止运行后, 线程对象仍在系统内中, 必须通过CloseHandle()
, 函数来关闭线程对象, 否则会在父进程结束的时候自动销毁;
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES secAttr,// 描述线程内核对象安全属性的指针,设置为NULL就会使用默认的安全描述符
SIZE_T stackSize,//表示线程栈空间大小如果为0那么和创建它的线程相同
LPTHREAD_START_ROUTINE threadFunc,//线程函数
LPVOID param,//线程函数参数列表
DWORD flags,//执行状态标志,0表示创建以后可以使用,CREATE_SUSPENDED线程挂起等待, 直到调用ResumeThread()
LPDWORD threadID//以长整型返回线程ID号,传入NULL表示不需要返回该线程ID
);
//线程函数的基本形式
DWORD WINAPI Thread1(LPVOID param);
-DWORD是数据类型,WINAPI是函数调用形式,返回32位数据的API函数。
-需要传递给新线程的任何参数都在CreateThread()的param中指定。线程函数在它的参数中接收这个32位的值。这个参数可以用作任何目的。函数返回它的退出状态。
- WINAPI是一个宏,所代表的符号是__stdcall。windows API函数采用这种标准调用约定。
#define WINAPI __stdcall
stdcall的调用约定意味着:
1)参数从右向左压入堆栈;
2)函数自身修改堆栈;
3) 函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
CreateThread()
是极其不安全, 容易造成数据的访问出现错误等原因
if(...)
{
switch(num)
{
/*code*/
}
}
在这个语句的时候如果我们调用多线程就会出现一个问题, 因为没有数据的加锁, 所以如果A线程进入了if
语句里面, 与此同时B线程进入, 那么会导致A的线程的num
变成了B线程或者相反的情况等
为了解决这个情况,Windows
操作系统提供了一种解决方案, 每个线程都有自己独立的内存区域,而这个内存区域由运行库函数_beginthreadex()
来负责
在另一篇博文中分析了
_beginthreadex
的源码和CreateThread
的区别, 水平有限只能看懂部分代码, 这里只是对他分析的结果进行自己理解的总结
_beginthreadex()
在创建线程时会分配初始化一个_tiddata
块, 这个初始化的块就是用来存储线程的独享数据, 新的线程运行的时候首先会将_tiddata
与自己关联起来, 把需要保护的数据存入到_tiddata
块中, 这样每个线程都只会访问和修改自己的数据而不会篡改其他线程的数据