本文将对线程的基本概念与创建方法进行介绍。
1.1 创建线程
在使用线程之前,我们需要学会创建一个新的线程,先介绍操作系统的接口,分Linux和Windows两种常用的操作系统来介绍。
-
创建线程
在使用线程之前,我们首先需要了解如何创建新线程。本节将分别介绍两种常用操作系统——Linux与Windows——所提供的线程创建接口。
Linux 线程的创建
在 Linux 系统中,我们使用 pthread_create 函数来创建线程,其函数原型如下:
int pthread_create(pthread_t * thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
其中,thread 参数为输出参数,用于返回创建成功的线程ID;attr 参数用于设置线程属性,传入 NULL 表示使用默认属性;start_routine 是线程函数的指针,其调用约定必须为 __cdecl(C Declaration 的缩写),这也是 C/C++ 中全局函数的默认调用约定。需要注意的是,在 Windows 系统中使用 CreateThread 创建线程时,线程函数必须显式声明为 __stdcall 调用约定。也就是说,默认调用约定的全局函数可以作为 Linux 中 pthread_create 的线程函数,但不能直接用于 Windows 的 CreateThread。
//代码片段1:不显示指定函数调用方式,其调用方式为默认的__cdecl
void* start_routine(void* args){
}
//代码片段2:显示指定函数调用方式__cdecl,等价于片段1
void* __cdecl strat_routine(void* args){
}
arg 参数用于向线程函数传递参数。由于其为 void* 类型,我们可以方便地传入任意类型的参数。若线程创建成功,函数返回 0;失败则返回相应的错误码,常见错误码包括 EAGAIN、EINVAL、EPERM 等。
以下是一个使用 pthread_create 创建线程的简单示例:
#include <pthread.h>
#include <unistd.h>
#include <iostream>
void* threadfunc(void* arg) {
while (1) {
sleep(1);
std::cout << "I am a New Thread!"std::endl;
}
return NULL;
}
int main() {
pthread_t threadID;
pthread_create(threadID, NULL, threadfunc, NULL);
while (1) {
}
return 0;
}
Windows 线程的创建
在 Windows 系统中,创建线程需使用 CreateThread 函数,其原型如下:
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
其中,lpThreadAttributes 表示线程的安全属性,通常设为 NULL;dwStackSize 指定线程栈大小(字节数),设为 0 表示使用默认大小;lpStartAddress 为线程函数地址。如前所述,Windows 中 CreateThread 要求线程函数必须使用 __stdcall 调用约定。因此,如下函数不能直接作为 CreateThread 的线程函数:
DWORD threadfunc(LPVOID lpThreadParameter);
如果不指定函数的调用方式,则使用默认调用方式__cdecl,而在Windows操作系统上要求使用__stdcall调用方式,因此必须显式声明调用方式为__stdcall:
DWORD __stdcall threadfunc(LPVOID lpThreadParameter);
在Windows操作系统上,WINAPI和CALLBACK这两个宏的值都是__stdcall,因此在很多项目中可以看到如下两种写法:
//写法1
DWORD WINAPI threadfunc(LPVOID lpThreadParameter);
//写法2
DWORD CALLBACK threadfunc(LPVOID lpThreadParameter);
参数lpParameter是传给线程函数的参数,和Linux的pthread_create函数的arg一样,实际上都是void类型,LPVOID类型实际上使用typedef包装后的void类型:
typedef void* LPVOID;
lpParameter 是传递给线程函数的参数,与 Linux 的 pthread_create 中的 arg 类似,类型为 LPVOID(即 void*)。dwCreationFlags 为线程创建标志,通常设为 0,表示线程创建后立即执行;若设为 CREATE_SUSPENDED(值为 4),则线程创建后处于挂起状态。lpThreadId 用于返回线程ID。
Windows 使用 HANDLE 类型管理线程对象。以下为在 Windows 系统中创建线程的示例代码:
#include <Windows.h>
#include <iostream>
DWORD WINAPI ThreadProc(LPVOID lpParamaters) {
while (true) {
Sleep(1000);
std::cout << "I am New Thread!" << std::endl;
}
return 0;
}
int main() {
DWORD dwThreadID;
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadID);
if (hThread == NULL) {
std::cout << "Failed to CreateThread." << std::endl;
return -1;
}
while (true) {
}
return 0;
}
C++11 提供的 std::thread 类
无论是 Linux 还是 Windows 的线程创建接口,其线程函数的签名都受到严格限制,对参数数量、类型和返回值类型均有特定要求。C++11 标准引入了 std::thread 类(需包含 <thread> 头文件),使我们能够将任意签名的函数作为线程函数。
以下示例创建两个线程,其线程函数签名不同:
#include <iostream>
#include <thread>
void threadproc1() {
while (true) {
std::cout << "I am New thread1!" << std::endl;
}
}
void threadproc2(int a,int b) {
while (true) {
std::cout << "I am New thread2!" << std::endl;
}
}
int main() {
//创建线程1
std::thread t1(threadproc1);
//创建线程2
std::thread t2(threadproc2, 1, 2);
while (true) {
}
return 0;
}
需要注意的是,使用 std::thread 时必须确保在线程函数执行期间,线程对象保持有效。以下是一个常见错误示例:
#include <iostream>
#include <thread>
void threadproc() {
while (true) {
std::cout << "I am New thread!" << std::endl;
}
}
void func() {
std::thread t(threadproc);
}
int main() {
func();
while (true) {
}
return 0;
}
该代码在 func() 返回后,线程对象 t 被销毁,而此时线程函数仍在运行,导致程序崩溃。因此,使用 std::thread 时应确保线程对象的生命周期覆盖线程函数的执行过程。