最近主管没有分配任务,于是将C/C++的线程学习一下。

经过了解才知道,C++03之前,用的创建线程都是CreateThread 与 _beginthreadex。使用这个两个函数进行创建线程。
然后C++11之后,就出现了新的线程函数thread,当然,这个创建线程比较方便!

经过两三天的纠结,最终决定深入研究_beginthreadex此方式创建线程,具体为什么我也说不清楚,看到网上很多人都推荐使用这个。。。

反正_beginthreadex内部都是调用CreateThread 进行创建线程的!


目录

  • 一、概念
  • 二、_beginthreadex创建线程
  • 三、单线程句柄阻塞
  • 四、多个线程句柄阻塞
  • 五、CreateThread创建线程
  • 六、thread创建线程
  • 七、总结

一、概念

简单说一下概念。

进程是包含线程的,一个进程可以有多个线程,多个线程(或一个)组合成一个进程。

线程是CPU调度和分派的基本单位,进程是分配资源的基本单位。

一个应用程序就是一个进程,例如运行一个QQ,运行一个微信等等,都是一个进程。
在QQ里面,我一边接收文件,一遍与别人聊天,这就是两个线程在同时运行!

为什么使用多线程

  1. 避免阻塞
    大家知道,单个进程只有一个主线程,当主线程阻塞的时候,整个进程也就阻塞了,无法再去做其它的一些功能了。
  2. 避免CPU空转
    应用程序经常会涉及到RPC,数据库访问,磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应时,CPU却不能去处理新的请求,导致这种单线程的应用程序性能很差。
  3. 提升效率
    一个进程要独立拥有4GB的虚拟地址空间,而多个线程可以共享同一地址空间,线程的切换比进程的切换要快得多。

头文件:
#include < process.h >


二、_beginthreadex创建线程

_beginthreadex
这里我们使用这个函数创建线程!

函数参数如下:

unsigned long _beginthreadex(void *security,         // 安全属性, 为NULL时表示默认安全性unsigned stack_size,    // 线程的堆栈大小, 一般默认为0unsigned(_stdcall *start_address)(void *),   // 线程函数void *argilist,       // 线程函数的参数unsigned initflag,        // 新线程的初始状态,0表示立即执行,//CREATE_SUSPENDED表示创建之后挂起unsigned *threaddr      // 用来接收线程ID);

返回值 : 成功返回新线程句柄, 失败返回0

创建线程:

int iParam = 10;
unsigned int dwThreadID;    // 接收线程的ID
HANDLE hThread;             // 线程句柄// 创建线程
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);// 线程执行的函数
unsigned int WINAPI  ThreadFun(LPVOID p) {int cnt = *((int *)p);// ......return 0;
}

第一第二个参数,默认写NULL和0就行了,第三个参数是线程执行的函数,第四个参数是函数的参数,第五个参数是指定线程立刻执行,第六个参数是获得线程的ID。

可以判断一下线程句柄,是否创建线程成功:

if (hThread == NULL) {printf("_beginthreadex() Error!\n");return -1;
}

如果函数需求是传入多个参数,那么只能使用结构体了!

然后线程执行完毕后要记得关闭线程句柄:

CloseHandle(hThread);

到此,一个简单的线程就已经创建完毕了!

测试代码:

#include <stdio.h>
#include <Windows.h>
#include <process.h>unsigned int WINAPI  ThreadFun(LPVOID p) {int cnt = *((int *)p);for (int i = 0; i < cnt; i++) {Sleep(1000);printf("Running thread!\n");}return 0;
}int main(void) {int iParam = 10;unsigned int dwThreadID;  // 线程idprintf("main begin\n");// 线程句柄             // 创建线程HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);if (hThread == NULL) {printf("_beginthreadex() Error!\n");return -1;}// 关闭CloseHandle(hThread);printf("main end\n");//Sleep(5000);system("pause");return 0;
}

注意:
代码中必须加上 system(“pause”);让主线程暂停在哪里等待子线程执行完毕,否则主线程执行完毕就直接接收程序了,子线程完全没有机会执行!
这里的mian函数就是主线程也就是进程,当进程结束后,会结束掉所有线程!

如下截图:


三、单线程句柄阻塞

上面第二点,如果我想等子线程执行完了之后再接着继续往下走执行printf(“main end\n”);
这需要怎么操作呢?

这就要用到阻塞了,就是阻塞主线程,子线程执行完毕后,再接着执行主线程。

使用这个函数:
WaitForSingleObject:等待一个内核对象变为已通知状态

WaitForSingleObject(_In_ HANDLE hHandle,       //指明一个内核对象的句柄_In_ DWORD dwMilliseconds     //等待时间
);

这样使用:

DWORD wr;// 等待通知,等待子线程执行完毕
// 阻塞主线程,INFINITE:等待特定秒数;WAIT_FAILED:WaitForSingleObject等待结束后会返回它
printf("WaitForSingleObject() begin\n");
if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) {printf("Thread wait error!\n");return -1;
}
printf("WaitForSingleObject() end\n");

WaitForSingleObject传入两个参数,参数一是线程句柄,参数二是等待时间,INFINITE表示很多秒,具体多少秒不知道,但是足以应付日常线程使用,也可以指定具体秒数。
如果发生错误会返回WAIT_FAILED,这样进行判断就可以知道等待是有没有发生错误了!

测试代码:

#include <stdio.h>
#include <Windows.h>
#include <process.h>// 线程执行的函数
unsigned int WINAPI  ThreadFun(LPVOID p) {int cnt = *((int *)p);for (int i = 0; i < cnt; i++) {Sleep(1000);printf("Running thread!\n");}return 0;
}int main(void) {int iParam = 10;unsigned int dwThreadID;  // 线程idDWORD wr;    // 阻塞线程返回值printf("main begin\n");// 线程句柄              // 创建线程HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void *)&iParam, 0, &dwThreadID);if (hThread == NULL) {printf("_beginthreadex() Error!\n");return -1;}// 等待通知,等待子线程执行完毕// 阻塞主线程,INFINITE:等待特定秒数;WAIT_FAILED:WaitForSingleObject等待结束后会返回它printf("WaitForSingleObject() begin\n");if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) {printf("Thread wait error!\n");return -1;}printf("WaitForSingleObject() end\n");CloseHandle(hThread);printf("main end\n");//Sleep(5000);//system("pause");    // 不需要再暂停了return 0;
}


四、多个线程句柄阻塞

提一个需求,创建n个线程,然后循环n次,奇数次对一个全局变量做加一操作,偶数次对一个全局变量做减一操作。
按照正常来讲,设置这个全局变量为零,当程序结束,这个全局变量也还是零才对,事实上是这样嘛?

使用WaitForMultipleObjects阻塞多个线程

WaitForMultipleObjects(_In_ DWORD nCount,            // 要监测的句柄的组的句柄的个数_In_reads_(nCount) CONST HANDLE* lpHandles,   //要监测的句柄的组_In_ BOOL bWaitAll,        // TRUE 等待所有的内核对象发出信号, FALSE 任意一个内核对象发出信号_In_ DWORD dwMilliseconds   //等待时间
);

使用如下:

// 阻塞多个线程句柄,直到子线程运行完毕,主线程才会往下走
WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);

参数一:检测句柄的个数;参数二:检测句柄的数组;参数三:TRUE等待所有线程执行完毕,FALSE,任意一个完成就停止阻塞;参数四:等待时间

测试代码:

#include <stdio.h>
#include <Windows.h>
#include <process.h>#define NUM_THREAD        50unsigned WINAPI threadInc(void *arg); // 减一操作
unsigned WINAPI threadDes(void *arg);   // 加一操作// 操作的全局变量
long long num = 0;int main(void) {// 创建n个线程句柄HANDLE tHandles[NUM_THREAD];printf("sizeof long long :%d\n", sizeof(long long));// 循环n次,创建n个线程for (int i = 0; i < NUM_THREAD; i++) {if (i % 2) {tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);} else {tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);}}// 阻塞多个线程句柄,直到子线程运行完毕,主线程才会往下走WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);for (int i = 0; i < NUM_THREAD; i++) {CloseHandle(tHandles[i]);}printf("result:%lld\n", num);return 0;
}// 对全局变量加一操作
unsigned WINAPI threadInc(void *arg) {for (int i = 0; i < 500000; i++) {num += 1;}return 0;
}// 对全局变量减一操作
unsigned WINAPI threadDes(void *arg) {for (int i = 0; i < 500000; i++) {num -= 1;}return 0;
}


可以看到,最终结果不是零,而是乱值,这是为什么???

以上图为例,假设全局变量num是99,线程一开始执行,获取它做加一操作,然后还没等线程一将数值还回去,线程二又获取它做操作,然后还回去,最后线程一才将数值还回去,所以导致计算结果不正确。
这里这是举一个例子啊,大致上表达的意思就是线程一和线程二公用同一块内存变量,导致数值计算不正确。

如果想要解决这个问题,那么必须使用:互斥对象CreateMutex,这个将在下一篇博客中讲解用法!


五、CreateThread创建线程

CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,//SDSIZE_T dwStackSize,//initialstacksizeLPTHREAD_START_ROUTINE lpStartAddress,//threadfunctionLPVOID lpParameter,//threadargumentDWORD dwCreationFlags,//creationoptionLPDWORD lpThreadId//threadidentifier
)

• 第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
• 第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。
• 第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
• 第四个参数 lpParameter 是传给线程函数的参数。
• 第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
• 第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

测试代码:

#include <stdio.h>
#include <Windows.h>
#include <process.h>DWORD WINAPI ThreadFun(LPVOID p) {double d = *((double *)p);printf("我是子线程, PID = %d\nd = %f\n", GetCurrentThreadId(), d);return 0;
}int main(void) {HANDLE hThread;DWORD dwThreadID;double p = 3.14;hThread = CreateThread(NULL, 0, ThreadFun, &p, 0, &dwThreadID);printf("我是主线程, PID = %d\n", GetCurrentThreadId());// 关闭线程CloseHandle(hThread);Sleep(2000);system("pause");return 0;
}


六、thread创建线程

#include <iostream>
#include <thread>
#include <Windows.h>using namespace std;void print1() {Sleep(3000);cout << "子线程1在运行。。。" << endl;
}void print2() {Sleep(3000);cout << "子线程2在运行。。。" << endl;
}class testThread {public:testThread() {// 这是一种重要用法,我在项目中常用t1 = new thread(&testThread::test, this);t1->detach();t2 = new thread(&testThread::test, this);t2->join();}~testThread() {if (t1) delete t1;if (t2) delete t2;}void test() {for (int i = 0; i < 10; i++) {cout << "正在运行:" << i << endl;Sleep(1000);}}private:thread *t1;thread *t2;
};int main(void) {// 创建线程thread test1(print1);thread test2(print2);// join函数,汇合线程,阻塞主线程,等待子线程执行结束,才会回到主线程中test1.join();// detch函数,分离,打破与主线程的依赖关系,即主线程运行主线程,子线程运行子线程,两个互不干扰test2.detach();cout << "主线程在运行..." << endl;// joinable函数,判断当前线程是否可以做join或者detach过程,可以返回true,不可以返回false.if (test2.joinable()) {test2.join();} else {cout << "子线程不可以做join或detch操作" << endl;}return 0;
}

七、总结

线程的创建就是这么简单,当然,可以根据自己的项目需求,有针对性的去学习哪一种线程,效果都是一样的!

C/C++ _beginthreadex 多线程操作相关推荐

  1. FMDatabaseQueue 数据库多线程操作、事务处理

    SQLite数据库多线程操作: 在上面一节中已经讲过FMDB的用法了,接下来讲讲sqlite在都线程中的用法.如果应用中使用了多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安 ...

  2. 一行 Python 实现并行化 -- 日常多线程操作的新思路 - 左手键盘,右手书 - SegmentFault...

    一行 Python 实现并行化 -- 日常多线程操作的新思路 - 左手键盘,右手书 - SegmentFault

  3. Shell多线程操作及线程数控制实例

    来源:http://www.jb51.net/article/51720.htm 这篇文章主要介绍了Shell多线程操作及线程数控制实例,文中从单线程实现一个需求开始,不断加入代码实现多线程以及线程数 ...

  4. python多线程读取数据库数据_python多线程操作MySQL数据库pymysql

    python多线程操作MySQL数据库pymysql 项目中使用多线程操作数据库提示错误:pymysql.err.InterfaceError: (0, "),原因是pymysql的exec ...

  5. MFC不能多线程操作控件的原因

    对于大多数mfc对象,请不要在线程间传递它们,不管是栈上的还是堆上的!原因如下:   mfc的大多数类不是线程安全的,调用传入对象的成员函数可能不会报错,但是未必能达到程序预定的功能!   mfc与界 ...

  6. python多线程读取数据库数据_Python基于多线程操作数据库相关知识点详解

    Python基于多线程操作数据库相关问题分析 本文实例分析了Python多线程操作数据库相关问题.分享给大家供大家参考,具体如下: python多线程并发操作数据库,会存在链接数据库超时.数据库连接丢 ...

  7. Core Data 多线程操作实战篇

    最近在解决百度音乐iPhone客户端偶现数据库操作crash的问题,顺手整理了下CoreData的多线程原则,以及实际开发时应该如何遵守这些原则. Core Data多线程操作的基本原则 不允许跨线程 ...

  8. c#多线程操作界面控件的简单实现

    一个小功能,早有人实现了.自己在一个项目中用到,觉得有必要记录一下,写下来. 代码 从上面你可能已经看出如何多线程操作同一个控件的,就是通过一个委托,然后定义委托方法,判断控件的InvokeRequi ...

  9. C#多线程操作界面控件的解决方案

    C#中利用委托实现多线程跨线程操作 - 张小鱼 2010-10-22 08:38 在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了 ...

最新文章

  1. 天才少女 12 岁被大学录取,希望 16 岁能成为 NASA 工程师
  2. GridView:根据单元格的值给单元格着色
  3. kubernetes 动态扩容pv
  4. Java黑皮书课后题第5章:**5.27(显示闰年)编写程序,显示从101到2100期间所有闰年,每行显示10个。数字之间用一个空格字符隔开,同时显示这期间闰年的数目
  5. 用UltraEdit判断打开文件的编码类型 用UltraEdit或notepad记事本查看文件编码格式 用UltraEdit查看当前文件编码...
  6. java代码实现通讯录实例,我不知道这有什么用。,
  7. wan口有流量但电脑上不了网_wan口有ip地址但是上不了网怎么办?
  8. r语言descstats_一条命令轻松绘制CNS顶级配图-ggpubr
  9. 使用kubectl访问Kubernetes集群时的身份验证和授权
  10. MAPZONE GIS SDK接入Openlayers3之二——空间参考扩展
  11. 液晶指定位置显示 c语言,大家看看该怎么改才能让12864液晶显示屏显示21个字符啊?...
  12. linux打包除了某个文件夹,tar打包且排除某个文件
  13. 解决Android 11 获取不到Serial number方法
  14. vue-oss阿里云上传图片
  15. EChart配置--基准线(坐标轴指示器axisPointer)的配置
  16. 如何u盘安装Linux系统CentOS7.2
  17. NewStarCTF2022-Week4-Web
  18. 视觉SLAM十四讲读书笔记(2)P10-P27
  19. 圣路易斯华盛顿大学计算机科学,圣路易斯华盛顿大学计算机科学硕士项目解析...
  20. 关于典型二阶系统固有频率

热门文章

  1. JsonPath文档
  2. hi mate, lets recall the bloody “JOIN“
  3. 计算机毕业设计 javaWeb图书销售管理系统的设计与实现(源码+论文)
  4. 批量提取pdf文件名到excel
  5. 【CNN-VGGNet】
  6. Overfeat,RCNN,Sppnet 2014年三篇经典文献及其创新思路总结
  7. 90后80后70后60后50后的无奈
  8. 3D轮播切换特效 源码+注释
  9. 如何实现思科新一代FPR防火墙零业务中断软件版本升级?
  10. mysql fulltext 没有结果_MySQL中的FULLTEXT搜索不返回任何行