1. 什么是线程? 为什么要用多线程?
线程,是操作系统能够运行和计算和调度的最小单位。一个进程至少要包含一个线程,线程要包含在进程中,是进程实际运作单位。一个进程中可以多有个线程,每个线程并发执行。每个线程有独立的栈空间,线程之间共享静态存储区和堆空间。
那么什么时候要用多线程呢? 当我们要使用并发的时候,就可以使用多线程。比如,我们要想要解码音视频的同时去播放音频和视频;打印机打印PDF并且界面显示当前进度和提示信息等等。
2. 线程的创建和使用
(1) 使用操作系统API创建
无论是Windows还是Linux,都提供了创建线程的API提供给我们去创建线程。Windows中使用API CreateThread 创建一个线程。它的具体表现形式如下:
HANDLE
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ __drv_aliasesMem LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
- lpThreadAttributes , 是线程的安全属性,一般设为 nullptr 。
- dwStackSize , 表示线程栈空间的大小,单位为字节。一般设置为0,表示默认大小。
- lpStartAddress , 表示新线程所执行的函数指针,类型为 LPTHREAD_START_ROUTINE 。其定义如下:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;
即,一个参数 LPVOID 类型,返回值为 DWORD 类型的函数指针。这里需要指出的是,函数需要使用 WINAPI 修饰,也就是要使用 __stdcall 来修饰函数。
- lpParameter ,传给线程的参数,LPVOID 类型本质上是 void * 。
- dwCreationFlags ,一般设置为0,表示线程创建后立即执行。如果设置为 CREATE_SUSPENDED ,表示使用 ResumeThread 时,线程才开始执行。
- lpThreadId ,输出参数,返回线程ID。
- 返回值: 返回线程句柄,如果失败返回空(nullptr)。
下面是一个简单的示例:
#include <stdlib.h>
#include <iostream>
#include <Windows.h>
// 线程中执行的函数
DWORD WINAPI threadProc(LPVOID lpParameters)
{
bool isRunThread = true;
while (isRunThread)
{
static int number = 0;
if (number >= 5)
{
// 仅执行5次打印
isRunThread = false;
break;
}
std::cout << "This is Run in Thread ID:" << ::GetCurrentThreadId() << ", Number is :" << number++ << std::endl;
Sleep(200);
}
return 0;
}
// Windows API 创建线程
HANDLE createWindowsThread(DWORD *threadId)
{
HANDLE handle = ::CreateThread(nullptr, 0, threadProc, nullptr, 0, threadId);
return handle;
}
int main(int argc, char** argv)
{
DWORD threadId = 0;
HANDLE threadHandle = createWindowsThread(&threadId);
if (threadHandle)
{
// 线程创建成功,打印创建的线程ID和主线程ID
std::cout << "Create Thread Success! Thread ID is " << threadId << std::endl;
std::cout << "Main Thread ID is " << ::GetCurrentThreadId() << std::endl;
// 等待线程结束
::WaitForSingleObject(threadHandle, INFINITE);
}
else // 线程创建失败
std::cout << "Create Thread Error!" << std::endl;
system("pause");
return 0;
}
函数执行结果如下:
Create Thread Success! Thread ID is 162840
Main Thread ID is 9652
This is Run in Thread ID:16284, Number is :0
This is Run in Thread ID:16284, Number is :1
This is Run in Thread ID:16284, Number is :2
This is Run in Thread ID:16284, Number is :3
This is Run in Thread ID:16284, Number is :4
当然也有可能是创建的线程中先打印,后者同时交替着打印。
函数 createWindowsThread 中使用了Windows的创建线程API CreateThread ,线程中执行函数 threadProc ,while 循环中执行5次后线程退出。
- GetCurrentThreadId 表示获取当前所在线程的 线程ID 。
- WaitForSingleObject 表示等待线程结束。这里第一个参数为线程的句柄,第二个参数用于表示等待的毫秒数,如果设置为 INFINITE ,表示无限等待下去。
(2) 使用C++11创建
不同操作系统的创建线程的函数是不同的,比如windows中使用 CreateThread ,而Linux中使用 pthread_create 创建。对于跨平台的开发,我们可以使用Qt等第三方开源库完成跨平台的线程创建,也可以使用C++11中提供的方法创建多线程。当前,使用的编译器要支持C++11。
可以使用C++11提供的 std::thread 实现线程的创建。
我们改写一下上面的例子,代码如下:
#include <stdlib.h>
#include <iostream>
#include <thread>
#include <Windows.h>
// 线程中执行的函数
void threadProc(int count)
{
bool isRunThread = true;
while (isRunThread)
{
static int number = 0;
if (number >= count)
{
// 仅执行Count次打印
isRunThread = false;
break;
}
std::cout << "This is Run in Thread ID:" << ::GetCurrentThreadId() << ", Number is :" << number++ << std::endl;
Sleep(200);
}
}
// C++11 创建线程
void createWindowsThread(void)
{
std::thread thread(threadProc, 5);
thread.detach();
}
int main(int argc, char** argv)
{
createWindowsThread();
system("pause");
return 0;
}
程序运行结果如下:
This is Run in Thread ID:10568, Number is :0
This is Run in Thread ID:10568, Number is :1
This is Run in Thread ID:10568, Number is :2
This is Run in Thread ID:10568, Number is :3
This is Run in Thread ID:10568, Number is :4
这里线程中执行的函数可以不指定为 __stdcall 修饰。创建 std::thread 对象,构造函数中传入线程中执行的函数指针和参数,如果没有参数可以不指定。就可以完成线程的创建。
- detach 表示线程与线程对象分离,线程资源的回收不受线程对象的控制。
如果想等待线程,可使用 join 函数,我们把代码修改如下:
// C++11 创建线程
std::thread createWindowsThread(void)
{
std::thread thread(threadProc, 5);
return thread;
}
int main(int argc, char** argv)
{
std::thread thread = createWindowsThread();
if (thread.joinable())
thread.join();
std::cout << "Created Thread Finished!" << std::endl;
system("pause");
return 0;
}
执行结果如下:
This is Run in Thread ID:10568, Number is :0
This is Run in Thread ID:10568, Number is :1
This is Run in Thread ID:10568, Number is :2
This is Run in Thread ID:10568, Number is :3
This is Run in Thread ID:10568, Number is :4
Created Thread Finished!
这里因为 std::thread 实现了移动构造,所以当函数中的线程对象被消除的时候,线程的相关信息转移给了新创建的线程函数。
- join 函数,表示阻塞等待线程的结束,如果线程结束则继续往下执行。joinable 方法可以判断能否被 join ,在 join 前需要调用该方法。
(3) c++11线程ID的获取
前面说了Windows的API获取线程ID的方法,那么如何使用C++11来获取线程ID呢?
- 可以使用 std::this_thread 类的 get_id 获取当前线程的ID,这是一个静态方法。
- 也可以使用 std::thread 对象的 get_id 获取线程ID。
get_id 函数返回的为一个 std::thread::id 的对象,无法直接通过该对象获取线程ID的整数值,但是该对象重载了 << 方法,如果想获取整数值需要借助 std::ostringstream 实现。添加头文件 #include <sstream>
具体实现代码如下:
int stdThreadIdToInt(const std::thread::id& id)
{
std::stringstream oss;
oss << id;
std::string string = oss.str();
int threadId = atol(string.c_str());
return threadId;
}
作者:douzhq
个人主页:http://www.douzhq.cn
文章同步页: http://www.douzhq.cn/thread_create/