C语言多线程编程 (一)

简单说明和使用C语言在window操作系统下创建多线程实现简单的示例操作。

创建进程

使用的是windows.h提供的创建进程函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void start_caculator()
{
DWORD dwExitCode;
PROCESS_INFORMATION pi;
DWORD ret;
STARTUPINFO si = { sizeof(si) };

char s[] = "calc.exe";
// 启动计算机
// C++不能字符串转换 修改工程属性 项目属性->高级->字符集->使用Unicode字符集改为未设置
ret = CreateProcess(NULL, s, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ret) {
// 等待子进程的退出
WaitForSingleObject(pi.hProcess, INFINITE);
// 关闭子进程的主线程语句
CloseHandle(pi.hThread);
// 获取子进程的退出码
GetExitCodeProcess(pi.hProcess, &dwExitCode);
// 关闭子进程句柄
CloseHandle(pi.hProcess);
}
cout << "\n进程结束 退出码是" << ret << endl;
}

其中主要的是:

1
ret = CreateProcess(NULL, s, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

参数说明:

  • lpApplicationName: 指定要运行的可执行文件的名称。在这种情况下,使用NULL表示使用命令行参数中指定的可执行文件
  • lpCommandLine: 指定要传递给可执行文件的命令行参数。在这种情况下,使用字符串变量s作为命令行参数。
  • lpProcessAttributes: 指定新进程的安全描述符。在这种情况下,使用NULL表示使用默认的安全描述符。
  • lpThreadAttributes: 指定新线程的安全描述符。在这种情况下,使用NULL表示使用默认的安全描述符。
  • bInheritHandles: 指定是否继承父进程的句柄。在这种情况下,使用FALSE表示不继承句柄。
  • dwCreationFlags: 指定控制新进程创建方式的标志。在这种情况下,使用0表示默认创建方式。
  • lpEnvironment: 指定新进程的环境块。在这种情况下,使用NULL表示使用父进程的环境块。
  • lpCurrentDirectory: 指定新进程的当前工作目录。在这种情况下,使用NULL表示使用父进程的当前工作目录。
  • lpStartupInfo: 指向一个STARTUPINFO结构,该结构包含了新进程的一些属性,例如窗口显示方式、标准输入输出重定向等。在这种情况下,使用si结构体。
  • lpProcessInformation: 指向一个PROCESS_INFORMATION结构,该结构接收新进程的标识信息,例如进程句柄和线程句柄。在这种情况下,使用pi结构体。

这里运行会打开计算机,当时主进程会直接结束,计算器仍保留。

疑问:这里主进程是等待子进程结束才会结束,而这个子进程是计算器本身这个进程,还是只开启计算器的进程?

个人理解是:IpCommandLine是传递命令参数,可能我们自己开启的进程的任务只是开启进程而不是计算器运行本身这个进程。

创建线程

定义线程结构体和启动线程两部分最重要。

1
HANDLE hTread = (HANDLE)_beginthreadex(NULL, 0, create_thread_task1, NULL, 0, NULL);

线程使用的是<process.h>头文件。

第三个参数是传递线程执行函数的函数指针。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int WINAPI create_thread_task1(LPVOID paramter)
{
while (1) {
cout << "child thread running ..." << endl;
Sleep(500);
}
return 0;
}
void create_thread_example_run()
{
// 句柄声明定义
// _beginthreadex()函数创建一个新的线程 指定子线程执行函数指针(函数类型固定) 返回线程的句柄
HANDLE hTread = (HANDLE)_beginthreadex(NULL, 0, create_thread_task1, NULL, 0, NULL);
while (1) {
cout << "main thread running ..." << endl;
Sleep(500);
}
// 句柄可以访问线程中各种系统资源 标识对象 访问对象 资源管理
// 通过CloseHandle函数关闭线程句柄 释放资源
CloseHandle(hTread);
}

这里线程执行的函数是固定的定义方式:

1
2
3
4
5
unsigned int WINAPI xxx(LPVOID paramter)
{
....
return 0;
}
  • LPVOID paramter:可以接收参数,通过创建线程执行语句的第4个参数
1
2
3
4
5
6
>参数1:指定线程安全特性 null 表示使用默认的安全特性
>参数2:指定线程的堆栈大小 通常设置为0 表示使用默认堆栈大小
>参数3:指向线程函数的指针
>参数4:传递给线程函数的参数 可以是任意类型的指针 接收是 void* 类型
>参数5:指定线程初始化标志 通常设置为0
>参数6:用于接收新线程的标识符

线程其他相关命令

  • 主线程等待子线程结束语句
1
WaitForSingleObject(hThread, INFINITE);
  • 获取当前线程的ID
1
GetCurrentThreadId()
  • 对于多个线程的处理,需要依次关闭线程,示例代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void create_multi_thread()
{
HANDLE handles[10];
for (int i = 0; i < 10; i++)
{
// 依次开启多个线程
handles[i] = (HANDLE)_beginthreadex(NULL, 0, thread_task3, NULL, 0, NULL);
}
WaitForMultipleObjects(10, handles, TRUE, INFINITE);
cout << "main thread ending ..." << endl;
for (int i = 0; i < 10; i++)
{
// 依次关闭多个线程
CloseHandle(handles[i]);
}
}
  • 主线程等待所有子线程结束
1
WaitForMultipleObjects(3, handles, TRUE, INFINITE);

补充知识

函数指针和函数指针数组,示例如下:

1
2
3
4
5
6
7
void thread_task21(){}

# 函数指针数组
void (*tasks[3])() = { thread_task1, thread_task2, thread_task3 };

# 使用
tasks[i]();