1、std::thread
C++11标准之后便引入了线程库std::thread。无论是windows或是Linux开发者都可以非常简单的通过这种方式,在C++程序中创建和管理线程。
示例代码:
#include <unistd.h>
#include <iostream>
#include <thread>
void thread_fun(void)
{
std::cout << "thread_fun id: " << std::this_thread::get_id() << std::endl;
sleep(1);
std::cout << "thread_fun exit." << std::endl;
}
int main(int argc, char *argv[])
{
std::cout << "main id: " << std::this_thread::get_id() << std::endl;
std::thread testthread(thread_fun);
testthread.join();
std::cout << "main exit." << std::endl;
return 0;
}
运行结果:
可以看到thread_fun处于另一个线程中执行。
且该子线程是一个阻塞的操作,主线程等待子线程返回后才继续往下执行,是因为我们对线程进行了join()操作,join()表示主线程要等待子线程返回后对其资源进行回收,所以它必须要等待子线程执行完成,才去执行join()后面的逻辑。因此,在实际的使用中调用join()的位置也是比较关键的。
当主线程不需要等待子线程的结果时,可以使用detach()分离操作。detach()会使子线程与主线程分离,不会阻塞主线程的运行,分离后便与主线程无关了,资源由系统回收。
示例代码:
#include <unistd.h>
#include <iostream>
#include <thread>
void thread_fun(void)
{
std::cout << "thread_fun id: " << std::this_thread::get_id() << std::endl;
sleep(1);
std::cout << "thread_fun exit." << std::endl;
}
int main(int argc, char *argv[])
{
std::cout << "main id: " << std::this_thread::get_id() << std::endl;
std::thread testthread(thread_fun);
testthread.detach();
std::cout << "main exit." << std::endl;
while (1)
{
pause();
}
return 0;
}
运行结果:
可以看到子线程没有阻塞主线程,detach()之后继续往下运行了。与join不同的是,在detach后面我们对主线程增加了while (1)的循环操作,防止主线程退出。如果不加循环等待会怎么样呢?
当把以上示例代码while (1)部分的代码删除,重新编译运行,会发现thread_fun子线程部分还没来得及执行,进程就直接退出了。
这是因为,detach()操作其实只是把子线程分离单独执行,而主线程的退出往往会伴随着整个进程退出,当进程退出时,所有的子线程也会随之终止。总的来说,即便子线程标记为detach()分离状态,也是与主线程“同生死,共存亡”的。
同时,C++标准库中也提供有一系列类库,用于实现多线程之间的数据同步及通信。如互斥锁std::mutex、条件变量std::condition_variable、互斥锁自动管理器std::lock_guard等。
2、pthread
对于Linux系统,pthread是Linux下的最常用的线程库,与std::thread不同,pthread是基于C语言实现。
有几个常用的数据类型及函数需要先了解一下:
pthread_t:标识线程ID。它是一个结构体数据类型,用于唯一标识一个线程。
pthread_attr_t:线程属性。可用于设置线程的堆栈地址、堆栈大小、优先级等。
pthread_mutex_t:互斥锁。用于线程同步,保护共享资源免受多个线程的同时访问。
pthread_create():创建一个线程。
pthread_self():获取当前线程ID。
pthread_join():阻塞当前的线程,直到另外一个线程运行结束。
pthread_detach():分离线程。类似于std::thread的detach()。
pthread_exit():终止当前线程。
pthread_attr_init():初始化线程属性。
pthread_attr_destroy():删除线程的属性。
pthread_attr_setschedparam():通过设置线程属性,设置线程优先级。
pthread_attr_setstacksize():通过设置线程属性,设置线程堆栈大小。
pthread_mutex_init():初始化互斥锁。
pthread_mutex_lock():互斥锁上锁。
pthread_mutex_unlock():互斥锁解锁。
pthread_mutex_destroy():销毁互斥锁。
以下通过一段示例代码,了解下pthread的常用用法:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
static void *pthread_func(void *param)
{
pthread_detach(pthread_self()); //分离线程
printf("pthread_func id=%ld\n", pthread_self()); //打印线程ID
int count = 0;
while(1)
{
sleep(2);
printf("count=%d\n", count++);
}
return NULL;
}
int main(int argc, char *argv[])
{
printf("main id=%ld\n", pthread_self());
pthread_t p_tid;
pthread_attr_t p_attr;
sched_param param;
param.sched_priority = 10;
pthread_attr_init(&p_attr); //初始化线程属性
pthread_attr_setschedparam(&p_attr, ¶m); //设置线程优先级为10
pthread_attr_setstacksize(&p_attr, 1024*1024); //设置堆栈大小为1M
pthread_create(&p_tid, &p_attr, pthread_func, NULL); //创建线程
while(1)
{
pause();
}
return 0;
}
通过以上代码便创建了一个线程,并将该线程与主线程分离,独立运行。需要注意的是,当需要把类成员函数作为线程函数的时候,需要把类成员函数声明为static静态函数才可。
当线程中涉及到数据共享时,就需要考虑到数据资源同步问题,这时就应该对同步的数据加锁处理。示例如下:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化
volatile int m_count = 0;
static void *pthread_func(void *param)
{
pthread_detach(pthread_self()); //分离线程
while(1)
{
sleep(1);
pthread_mutex_lock(&m_mutex);
printf("pthread_func id=%ld m_count=%d\n", pthread_self(), m_count++);
pthread_mutex_unlock(&m_mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
printf("main id=%ld\n", pthread_self());
//pthread_mutex_init(&m_mutex, NULL); //动态初始化
pthread_t p_tid;
pthread_attr_t p_attr;
sched_param param;
param.sched_priority = 10;
pthread_attr_init(&p_attr); //初始化线程属性
pthread_attr_setschedparam(&p_attr, ¶m); //设置线程优先级为10
pthread_attr_setstacksize(&p_attr, 1024*1024); //设置堆栈大小为1M
pthread_create(&p_tid, &p_attr, pthread_func, NULL); //创建线程1
pthread_create(&p_tid, &p_attr, pthread_func, NULL); //创建线程2
while(1)
{
pause();
}
return 0;
}
对于pthread_mutex_t互斥锁的初始化有两种方式,静态初始化和动态初始化。动态初始化即使用上述提及的pthread_mutex_init()函数,静态初始化则在编译时初始化锁为解锁状态。
3、CreateThread
CreateThread 函数是 Windows API 中用于创建新线程的一个非常重要的函数。在 Win32 编程中,线程是系统分配处理器时间资源的基本单元,它是进程中的实际运作单位。通过使用 CreateThread可以在同一进程中创建多个线程。
CreateThread 函数原型如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数说明:
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,用于指定线程的安全属性。如果为 NULL,则线程将使用默认安全属性。
dwStackSize:指定线程堆栈的初始大小(以字节为单位)。如果设置为 0,则堆栈大小将与创建它的线程的堆栈大小相同。
lpStartAddress:指向线程函数的指针,表示新线程的入口点。
lpParameter:传递给线程函数的参数。
dwCreationFlags:控制线程创建的标志。它的值可以是以下两种之一: 0(表示线程创建后立即运行)或CREATE_SUSPENDED(表示线程创建后暂停运行,直到调用ResumeThread函数)。
lpThreadId:指向DWORD变量的指针,用于接收新线程的标识符。如果为NULL,则不返回线程标识符。
返回值:
如果函数执行成功,CreateThread 返回一个指向新线程的句柄。如果函数调用失败,则返回值为 NULL。
示例代码:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{
printf("Hello!!!\n");
return 0;
}
int main(int argc, char *argv[])
{
HANDLE hThread;
DWORD threadId;
//创建线程
hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &threadId);
if (hThread == NULL)
{
printf("创建线程失败 (%d)\n", GetLastError());
return -1;
}
//等待线程结束
WaitForSingleObject(hThread, INFINITE);
//关闭线程句柄
CloseHandle(hThread);
return 0;
}