线程的基本操作

48 阅读4分钟

本文将对线程的基本概念与创建方法进行介绍。

1.1 创建线程

在使用线程之前,我们需要学会创建一个新的线程,先介绍操作系统的接口,分Linux和Windows两种常用的操作系统来介绍。

  1. 创建线程

    在使用线程之前,我们首先需要了解如何创建新线程。本节将分别介绍两种常用操作系统——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;失败则返回相应的错误码,常见错误码包括 EAGAINEINVALEPERM 等。

以下是一个使用 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 表示线程的安全属性,通常设为 NULLdwStackSize 指定线程栈大小(字节数),设为 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 时应确保线程对象的生命周期覆盖线程函数的执行过程。