- C
windows C语言多线程的教程 通俗易懂 快速上手
- 2025-2-21 19:59:48 @
小朋友,我们来一起学习Windows下C语言的多线程哦,就像玩游戏一样有趣!
1. 准备工作
首先呢,我们要在电脑上安装一个能写C语言程序的软件,比如Visual Studio。就好像画画得先有画笔和纸一样,有了这个软件,我们才能写代码。
2. 多线程是什么
想象一下,你有一个大任务,比如整理好多玩具。如果只有你一个人做,可能要花很久。但要是有几个小伙伴一起帮忙,每个人负责一部分,就能很快完成啦。在电脑程序里也是这样,多线程就是让几个“小伙伴线程”一起做事,让程序跑得更快,能同时做更多的任务。
3. 开始写代码啦
打开我们安装好的软件,新建一个C语言项目。然后在代码文件里,我们要先写一些“魔法咒语”,也就是头文件:
#include <windows.h>
#include <stdio.h>
#include <windows.h>
就像是打开了Windows世界的大门,让我们能用Windows系统提供的各种工具;#include <stdio.h>
是为了能在屏幕上显示文字和接收我们输入的东西。
4. 创造线程小伙伴
我们要写一个“小伙伴线程”要做的事情,这就像给小伙伴安排任务。比如,我们让它在屏幕上打印一句话:
// 线程要做的事情,就像小伙伴的任务
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
printf("我是新线程,我在工作啦!\n");
return 0;
}
这里的 DWORD WINAPI
是一种规定的写法,告诉电脑这是线程要做的函数。MyThreadFunction
是我们给这个任务起的名字,LPVOID lpParam
是用来给这个任务传递东西的(现在我们先不管它)。
接下来,在 main
函数里,我们要把这个小伙伴线程创造出来:
int main() {
HANDLE hThread; // 这是用来记住我们创造的线程的
DWORD threadId; // 线程的身份证号
// 创造线程小伙伴
hThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId);
if (hThread == NULL) {
printf("创造线程失败啦,呜呜\n");
return 1;
}
// 等线程小伙伴把事情做完
WaitForSingleObject(hThread, INFINITE);
// 用完线程小伙伴后,把它的“房间”打扫干净
CloseHandle(hThread);
printf("我是主线程,我也做完啦!\n");
return 0;
}
在 CreateThread
这里,我们就像在召唤一个线程小伙伴出来。NULL
就像一些默认的设置,0
是给线程的一些资源大小,MyThreadFunction
就是我们之前给它安排的任务,后面的 NULL
是给任务传递的东西(我们还没用到),&threadId
是把线程的身份证号存起来。
WaitForSingleObject
是让主线程(就像小组长)等这个线程小伙伴把任务做完。最后 CloseHandle
就像是把线程小伙伴用过的东西收拾好,释放资源。
5. 给线程小伙伴传递东西
有时候,我们要给线程小伙伴一些东西让它处理。比如我们给它一个数字,让它打印出来:
#include <windows.h>
#include <stdio.h>
// 线程要做的事情,现在能接收东西啦
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
int num = *(int*)lpParam;
printf("我收到的数字是:%d\n", num);
return 0;
}
int main() {
HANDLE hThread;
DWORD threadId;
int number = 5; // 我们准备给线程的数字
// 创造线程小伙伴,把数字传过去
hThread = CreateThread(NULL, 0, MyThreadFunction, &number, 0, &threadId);
if (hThread == NULL) {
printf("创造线程失败啦,呜呜\n");
return 1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
printf("我是主线程,我也做完啦!\n");
return 0;
}
这里我们把 number
的地址(&number
)传给线程,线程里通过 *(int*)lpParam
把这个数字拿出来用。
6. 让线程小伙伴们排队
当有好几个线程小伙伴都要访问同一个东西(比如一个玩具大家都想玩),就可能会乱套。这时候我们要让它们排队,这就用到互斥锁(Mutex)。
#include <windows.h>
#include <stdio.h>
HANDLE hMutex; // 这是我们的排队工具,互斥锁
int sharedVariable = 0; // 这是大家都想访问的东西,就像那个玩具
// 线程要做的事情
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
for (int i = 0; i < 3; ++i) {
WaitForSingleObject(hMutex, INFINITE); // 排队,等轮到自己
sharedVariable++;
printf("线程 %d 让数字变成了:%d\n", GetCurrentThreadId(), sharedVariable);
ReleaseMutex(hMutex); // 用完了,让给别人
}
return 0;
}
int main() {
HANDLE hThread1, hThread2;
DWORD threadId1, threadId2;
// 创建排队工具,互斥锁
hMutex = CreateMutex(NULL, FALSE, NULL);
if (hMutex == NULL) {
printf("创建排队工具失败啦,呜呜\n");
return 1;
}
// 创造两个线程小伙伴
hThread1 = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId1);
hThread2 = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId2);
if (hThread1 == NULL || hThread2 == NULL) {
printf("创造线程失败啦,呜呜\n");
CloseHandle(hMutex);
return 1;
}
// 等两个线程小伙伴做完
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
// 把排队工具和线程小伙伴都收拾好
CloseHandle(hThread1);
CloseHandle(hThread2);
CloseHandle(hMutex);
printf("我是主线程,我也做完啦!\n");
return 0;
}
CreateMutex
是创建排队工具,WaitForSingleObject
是去排队,ReleaseMutex
是用完后离开队伍,让别人能进来。
小朋友,你可以试着按照这些步骤,在电脑上敲敲代码,看看线程小伙伴们是怎么工作的哦!
4 条评论
-
admin SU @ 2025-2-21 20:53:28
小朋友,下面我们来一起学习Windows系统里用C语言写多线程程序哦,保证你能很快学会!
1. 准备写代码的地方
我们得先在电脑上安装一个叫Visual Studio的软件,它就像一个超级厉害的魔法盒子,能帮我们写C语言程序。安装好之后,打开它,新建一个C语言项目,这就好比准备好了画画的纸啦。
2. 多线程是什么呢
想象一下,你要写一篇很长的作文,又要给作文配好看的画。要是你一个人做,一会儿写作文,一会儿画画,可能会有点乱,而且花的时间也长。但要是有两个你,一个专门写作文,一个专门画画,就能同时做这两件事,很快就完成啦。在电脑的程序里呀,多线程就像是有好几个“你”一起工作,每个“你”(线程)都能做不同的任务,这样程序就能做得又快又好啦。
3. 开始写代码
在新建的项目代码文件里,我们要先写一些神奇的“咒语”,也就是头文件:
#include <windows.h> #include <stdio.h>
#include <windows.h>
就像是打开了Windows这个大城堡的大门,有了它,我们才能用城堡里各种各样的工具,比如创建线程的工具。#include <stdio.h>
是为了能在屏幕上显示我们写的字,还有接收我们从键盘输入的东西。4. 创造线程小帮手
我们要写一个让线程小帮手做的事情,就像给小帮手布置任务。比如,让它在屏幕上跟我们打个招呼:
// 这是线程小帮手要做的事情哦 DWORD WINAPI MyThreadFunction(LPVOID lpParam) { printf("我是新线程,我开始工作啦!\n"); return 0; }
这里的
DWORD WINAPI
是一种规定的写法,就像游戏里的规则一样,告诉电脑这是线程要做的函数。MyThreadFunction
是我们给这个任务起的名字,很好记吧。LPVOID lpParam
是用来给这个任务传递东西的,不过现在我们先不管它哦。接下来,在
main
函数里,我们要把这个线程小帮手召唤出来:int main() { HANDLE hThread; // 这是用来记住我们创造的线程的,就像给它一个专属标记 DWORD threadId; // 这是线程的身份证号 // 开始召唤线程小帮手 hThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId); if (hThread == NULL) { printf("召唤线程失败啦,呜呜\n"); return 1; } // 等线程小帮手把事情做完 WaitForSingleObject(hThread, INFINITE); // 用完线程小帮手后,把它的“工具”收拾好 CloseHandle(hThread); printf("我是主线程,我也做完啦!\n"); return 0; }
在
CreateThread
这里,我们就像在念召唤咒语。NULL
就像是一些默认的设置,不用我们操心。0
是给线程的一些资源大小,现在我们也不用太懂。MyThreadFunction
就是我们之前给线程小帮手布置的任务。后面的NULL
是给任务传递的东西,我们还没用到呢。&threadId
是把线程的身份证号存起来,以后就能找到它啦。WaitForSingleObject
是让主线程(就像小组长)等这个线程小帮手把任务做完。最后CloseHandle
就像是把线程小帮手用过的工具收拾好,不能乱扔哦,这样就能释放资源啦。5. 给线程小帮手送东西
有时候,我们要给线程小帮手一些东西让它处理。比如给它一个数字,让它告诉我们这个数字是多少:
#include <windows.h> #include <stdio.h> // 线程小帮手要做的事情,现在能接收东西啦 DWORD WINAPI MyThreadFunction(LPVOID lpParam) { int num = *(int*)lpParam; printf("我收到的数字是:%d\n", num); return 0; } int main() { HANDLE hThread; DWORD threadId; int number = 5; // 我们准备给线程的数字 // 召唤线程小帮手,把数字传过去 hThread = CreateThread(NULL, 0, MyThreadFunction, &number, 0, &threadId); if (hThread == NULL) { printf("召唤线程失败啦,呜呜\n"); return 1; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); printf("我是主线程,我也做完啦!\n"); return 0; }
这里我们把
number
的地址(&number
)传给线程小帮手,它就能通过*(int*)lpParam
把这个数字拿出来用啦。6. 让线程小帮手们排队
要是有好多线程小帮手都想玩同一个玩具(就像访问同一个东西),可能就会乱起来。这时候我们得让它们排队,这就要用到互斥锁(Mutex)啦。
#include <windows.h> #include <stdio.h> HANDLE hMutex; // 这是我们的排队工具,互斥锁 int sharedVariable = 0; // 这是大家都想玩的玩具,也就是大家都想访问的东西 // 线程小帮手要做的事情 DWORD WINAPI MyThreadFunction(LPVOID lpParam) { for (int i = 0; i < 3; ++i) { WaitForSingleObject(hMutex, INFINITE); // 排队,等轮到自己 sharedVariable++; printf("线程 %d 让数字变成了:%d\n", GetCurrentThreadId(), sharedVariable); ReleaseMutex(hMutex); // 用完了,让给别人 } return 0; } int main() { HANDLE hThread1, hThread2; DWORD threadId1, threadId2; // 创建排队工具,互斥锁 hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { printf("创建排队工具失败啦,呜呜\n"); return 1; } // 召唤两个线程小帮手 hThread1 = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId1); hThread2 = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, &threadId2); if (hThread1 == NULL || hThread2 == NULL) { printf("召唤线程失败啦,呜呜\n"); CloseHandle(hMutex); return 1; } // 等两个线程小帮手做完 WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); // 把排队工具和线程小帮手都收拾好 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hMutex); printf("我是主线程,我也做完啦!\n"); return 0; }
CreateMutex
是创建排队工具,就像做了一个排队的栏杆。WaitForSingleObject
是让线程小帮手去排队,等轮到自己才能玩玩具。ReleaseMutex
是用完玩具后离开队伍,把玩具让给别人。小朋友,现在你可以按照这些步骤,在电脑上试着敲敲代码,看看线程小帮手们是怎么一起工作的哟!
-
2025-2-21 20:14:53@
#include <windows.h> #include <stdio.h> // 全局变量,用于模拟一直增加的值 int value = 0; // 用于线程同步的互斥锁 HANDLE hMutex; // 负责让值一直增加的线程函数 DWORD WINAPI IncreaseThread(LPVOID lpParam) { while (true) { WaitForSingleObject(hMutex, INFINITE); value++; ReleaseMutex(hMutex); Sleep(1000); // 暂停1秒,让增加的效果明显些 } return 0; } // 负责接收输入的线程函数 DWORD WINAPI InputThread(LPVOID lpParam) { int input; while (true) { scanf_s("%d", &input); // 在Windows下使用scanf_s更安全 // 这里可以对输入的input进行处理,比如打印出来 printf("你输入的值是:%d\n", input); printf("时间是:%d\n", value); } return 0; } int main() { HANDLE hThread1, hThread2; DWORD threadId1, threadId2; // 创建互斥锁 hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { printf("创建互斥锁失败\n"); return 1; } // 创建让值增加的线程 hThread1 = CreateThread(NULL, 0, IncreaseThread, NULL, 0, &threadId1); if (hThread1 == NULL) { printf("创建增加线程失败\n"); CloseHandle(hMutex); return 1; } // 创建接收输入的线程 hThread2 = CreateThread(NULL, 0, InputThread, NULL, 0, &threadId2); if (hThread2 == NULL) { printf("创建输入线程失败\n"); CloseHandle(hThread1); CloseHandle(hMutex); return 1; } // 等待线程结束(这里实际上不会结束,只是防止主线程过早退出) WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); // 关闭线程句柄和互斥锁 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hMutex); return 0; }
-
2025-2-21 20:01:52@
以下是对上述代码每个部分作用的详细解释:
头文件引入
#include <windows.h> #include <stdio.h>
<windows.h>
:这是Windows操作系统提供的非常重要的头文件,它包含了大量与Windows系统编程相关的定义、结构体声明和函数原型。在进行Windows下的多线程编程时,诸如线程创建函数CreateThread
、同步对象相关函数等都在这个头文件中声明,因此必须引入。<stdio.h>
:标准输入输出头文件,提供了像printf
(用于输出信息到控制台)和scanf_s
(在Windows下更安全的输入函数,用于从控制台读取用户输入)等函数的声明,方便在程序中进行输入输出操作。
全局变量和互斥锁声明
// 全局变量,用于模拟一直增加的值 int value = 0; // 用于线程同步的互斥锁 HANDLE hMutex;
value
:一个全局整型变量,用于模拟在程序运行过程中一直增加的值。由于多个线程可能会同时访问和修改它,所以需要采取同步措施来保证数据的一致性。hMutex
:声明了一个互斥锁句柄,类型为HANDLE
。互斥锁是一种用于线程同步的机制,确保在同一时刻只有一个线程能够访问被保护的共享资源(这里就是value
变量),防止出现数据竞争和不一致的情况。
线程函数定义
让值一直增加的线程函数
// 负责让值一直增加的线程函数 DWORD WINAPI IncreaseThread(LPVOID lpParam) { while (true) { WaitForSingleObject(hMutex, INFINITE); value++; ReleaseMutex(hMutex); Sleep(1000); // 暂停1秒,让增加的效果明显些 } return 0; }
DWORD WINAPI
:这是Windows下定义线程函数的特定调用约定和返回类型。DWORD
表示无符号长整型,作为函数的返回值类型;WINAPI
是一种宏定义,指定了函数的调用约定,它会影响函数参数的传递方式和堆栈的清理方式。IncreaseThread
:线程函数的名称,是我们自己定义的,这个线程的主要任务是让value
变量不断增加。LPVOID lpParam
:函数的参数,类型为LPVOID
(即void*
,通用指针类型),用于接收从CreateThread
函数传递过来的参数。在这个函数中暂时没有使用该参数。while (true)
:创建一个无限循环,使得该线程持续运行,不断执行后续的操作。WaitForSingleObject(hMutex, INFINITE)
:调用该函数来等待获取互斥锁hMutex
。INFINITE
表示无限等待,直到成功获取互斥锁。当一个线程调用此函数并获取到互斥锁后,其他线程再调用该函数时就会被阻塞,直到拥有互斥锁的线程释放它。value++
:对全局变量value
进行自增操作,这是该线程的核心任务。由于在获取互斥锁之后执行,所以能保证同一时刻只有一个线程在修改value
,避免数据竞争。ReleaseMutex(hMutex)
:在完成对value
的操作后,调用此函数释放互斥锁,让其他等待获取互斥锁的线程有机会获取并访问共享资源。Sleep(1000)
:使当前线程暂停执行1000毫秒(即1秒),这样可以让value
增加的效果在控制台输出时更加明显,便于观察。
负责接收输入的线程函数
// 负责接收输入的线程函数 DWORD WINAPI InputThread(LPVOID lpParam) { int input; while (true) { scanf_s("%d", &input); // 在Windows下使用scanf_s更安全 // 这里可以对输入的input进行处理,比如打印出来 printf("你输入的值是:%d\n", input); } return 0; }
InputThread
:线程函数的名称,该线程的主要功能是接收用户从控制台输入的数据。- 函数内部先声明了一个整型变量
input
,用于存储用户输入的值。 while (true)
:同样创建一个无限循环,使得该线程能够持续监听用户的输入。scanf_s("%d", &input)
:使用scanf_s
函数从控制台读取用户输入的一个整数,并将其存储到input
变量中。在Windows平台上,scanf_s
比传统的scanf
更安全,它要求指定读取字符串等数据时的缓冲区大小,以防止缓冲区溢出等安全问题。printf("你输入的值是:%d\n", input);
:将用户输入的值打印输出到控制台,这里只是简单的示例处理,实际应用中可以根据需求对输入值进行更复杂的处理,比如根据输入值改变程序的运行逻辑等。
主函数部分
int main() { HANDLE hThread1, hThread2; DWORD threadId1, threadId2; // 创建互斥锁 hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { printf("创建互斥锁失败\n"); return 1; } // 创建让值增加的线程 hThread1 = CreateThread(NULL, 0, IncreaseThread, NULL, 0, &threadId1); if (hThread1 == NULL) { printf("创建增加线程失败\n"); CloseHandle(hMutex); return 1; } // 创建接收输入的线程 hThread2 = CreateThread(NULL, 0, InputThread, NULL, 0, &threadId2); if (hThread2 == NULL) { printf("创建输入线程失败\n"); CloseHandle(hThread1); CloseHandle(hMutex); return 1; } // 等待线程结束(这里实际上不会结束,只是防止主线程过早退出) WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); // 关闭线程句柄和互斥锁 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hMutex); return 0; }
- 首先声明了四个变量:
hThread1
和hThread2
:类型为HANDLE
,用于存储创建的两个线程的句柄。线程句柄是一个标识线程的对象,通过它可以对线程进行各种操作,如等待线程结束、关闭线程等。threadId1
和threadId2
:类型为DWORD
,用于存储两个线程的唯一标识符。
hMutex = CreateMutex(NULL, FALSE, NULL);
:调用CreateMutex
函数创建一个互斥锁对象,并将返回的互斥锁句柄赋值给hMutex
。三个参数分别表示:- 第一个
NULL
:表示使用默认的安全属性。 FALSE
:表示互斥锁初始状态为未被任何线程拥有。- 第二个
NULL
:表示互斥锁没有名称(如果为互斥锁指定名称,不同进程中的线程也可以通过名称访问该互斥锁)。
- 第一个
- 接下来是错误处理,如果
hMutex
为NULL
,说明创建互斥锁失败,打印错误信息并返回1,终止程序。 hThread1 = CreateThread(NULL, 0, IncreaseThread, NULL, 0, &threadId1);
:调用CreateThread
函数创建第一个线程,即让值增加的线程:- 第一个
NULL
:表示使用默认的线程安全属性。 0
:表示使用默认的线程堆栈大小。IncreaseThread
:指定线程启动后要执行的函数。- 第二个
NULL
:表示不向线程函数传递参数。 0
:表示线程创建后立即运行。&threadId1
:用于接收新创建线程的标识符。
- 第一个
- 同样有错误处理,如果
hThread1
为NULL
,说明创建该线程失败,打印错误信息,关闭之前创建的互斥锁,并返回1终止程序。 - 以类似的方式,
hThread2 = CreateThread(NULL, 0, InputThread, NULL, 0, &threadId2);
创建第二个线程,即负责接收输入的线程,并进行相应的错误处理。 WaitForSingleObject(hThread1, INFINITE);
和WaitForSingleObject(hThread2, INFINITE);
:这两个函数调用分别让主线程等待hThread1
和hThread2
所代表的线程执行结束。由于这两个线程内部都是无限循环,实际上不会结束,但这样可以防止主线程过早退出,导致子线程还没来得及执行就被终止。CloseHandle(hThread1);
、CloseHandle(hThread2);
和CloseHandle(hMutex);
:分别关闭两个线程的句柄和互斥锁句柄,释放相关资源,确保程序不会出现资源泄漏的问题。- 最后
return 0;
表示主线程正常结束。
-
2025-2-21 20:01:13@
在Windows的C语言编程中,要实现在主体程序运行时还能进行输入,不被输入操作阻断,可以使用多线程的方式,以下是示例代码:
#include <windows.h> #include <stdio.h> // 全局变量,用于模拟一直增加的值 int value = 0; // 用于线程同步的互斥锁 HANDLE hMutex; // 负责让值一直增加的线程函数 DWORD WINAPI IncreaseThread(LPVOID lpParam) { while (true) { WaitForSingleObject(hMutex, INFINITE); value++; ReleaseMutex(hMutex); Sleep(1000); // 暂停1秒,让增加的效果明显些 } return 0; } // 负责接收输入的线程函数 DWORD WINAPI InputThread(LPVOID lpParam) { int input; while (true) { scanf_s("%d", &input); // 在Windows下使用scanf_s更安全 // 这里可以对输入的input进行处理,比如打印出来 printf("你输入的值是:%d\n", input); } return 0; } int main() { HANDLE hThread1, hThread2; DWORD threadId1, threadId2; // 创建互斥锁 hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { printf("创建互斥锁失败\n"); return 1; } // 创建让值增加的线程 hThread1 = CreateThread(NULL, 0, IncreaseThread, NULL, 0, &threadId1); if (hThread1 == NULL) { printf("创建增加线程失败\n"); CloseHandle(hMutex); return 1; } // 创建接收输入的线程 hThread2 = CreateThread(NULL, 0, InputThread, NULL, 0, &threadId2); if (hThread2 == NULL) { printf("创建输入线程失败\n"); CloseHandle(hThread1); CloseHandle(hMutex); return 1; } // 等待线程结束(这里实际上不会结束,只是防止主线程过早退出) WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); // 关闭线程句柄和互斥锁 CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hMutex); return 0; }
代码说明
- 多线程创建:使用
CreateThread
函数分别创建了两个线程,一个线程函数IncreaseThread
用于让全局变量value
不断增加,另一个线程函数InputThread
用于接收用户的输入。 - 线程同步:通过创建互斥锁
hMutex
,在线程中使用WaitForSingleObject
和ReleaseMutex
来保证对全局变量value
的访问是安全的,避免多线程同时访问时出现数据错误。 - 输入处理:在
InputThread
线程函数中,使用scanf_s
(Windows下更安全的输入函数)接收用户输入,并对输入进行简单处理(这里只是打印出来)。
这样,两个线程可以同时运行,实现了主体程序(这里是让值增加的部分)运行的同时还能接收用户输入 。
- 多线程创建:使用
- 1