Posix线程/C++11多线程
本文对比了POSIX pthreads和C++11 threads在多线程编程中的应用,包括线程创建、
线程组、线程传参及类成员函数作为线程函数的实现,适合初学者和开发者深入理解C++多线程
编程。
在C++11引进多线程之前,我们不得不用POSIX pthreads来编写多线程程序。
POSIX pthreads 是POSIX标准的一部分,它提供了一组函数和宏,用于创建和管理线程。
虽然 POSIX pthreads 功能强大,但它的API相对复杂,使用起来比较繁琐。
本文包括了以下内容:
- POSIX多线程实践
- C++11多线程实践
- 类成员函数作为线程函数的实现
一、POSIX多线程实践
创建简单线程
创建一个线程,用pthread_join()阻塞,等待线程结束。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void *callback(void*)
{
cout << "子线程ID: " << pthread_self() << endl;//获取当前线程ID
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,callback,NULL);
pthread_join(tid,NULL);
return 0;
}
多个线程实现
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
static const int NUM_THREADS = 5; //线程数量
void *call_from_thread(void *arg) {
cout << "Hello from thread: " << pthread_self() << endl;
return NULL;
}
int main()
{
pthread_t t[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++)
{
pthread_create(&t[i],NULL,call_from_thread,NULL);
}
for (int i = 0; i < NUM_THREADS; i++)
{
pthread_join(t[i],NULL);//等待线程结束
}
return 0;
}
当创建多个子线程时,外加主线程共六个线程,我们可以看到线程之间是平等竞争临界资源关系的,顺序是无序的,因此如果要明确确定顺序,则需要同步术语来改进,如互斥锁、条件变量等。
线程传参的实现
线程的传参可以是简单的数据类型,也可以是自定义数据类型,因此我们在编写线程传参的时候要留意。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
static const int NUM_THREADS = 5;
struct thread_data {
int thread_id;
};
void *call_from_thread(void *arg) {
struct thread_data *th_data = (struct thread_data *)arg;
cout << "Hello from thread " << th_data->thread_id << endl;
return NULL;
}
int main()
{
pthread_t t[NUM_THREADS];
thread_data td[NUM_THREADS];
for(int i=0;i<NUM_THREADS;i++){
td[i].thread_id = i;
pthread_create(&t[i],NULL,call_from_thread,&td[i]); //td[i]作为参数传递给call_from_thread函数
}
cout << "Hello from main thread" << endl;
for(int i=0;i<NUM_THREADS;i++){
pthread_join(t[i],NULL);//等待线程结束
}
return 0;
}
再一次验证了,线程之间是竞争、无序的。
二、C++11多线程实践
C++11增加了多线程,能够更简便的实现多线程编程,并且兼容POSIX pthreads。
简单线程
#include <iostream>
#include <thread>
#include <string>
using namespace std;
//普通函数作为线程函数
void call_from_thread() {
// 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
cout << "Hello from thread" << endl;
}
int main()
{
thread tid(call_from_thread); //创建、启动线程
tid.join(); //等待线程结束
cout << "Hello from main thread" << endl;
//lambda表达式作为线程函数
auto f = [](const std::string& name){
cout << "Hello" << name << endl;
};
thread tid2(f,"Tom"); //创建、启动线程
tid2.join(); //等待线程结束
return 0;
}
C++11线程其实是和POSIX线程API创建的线程本质上是一样的,只是C++11线程封装了POSIX线程API,并且提供了更简洁的接口。
main函数创建了线程call_back函数,并在tid.join()处等待线程结束。如果忘记等待,main线程有可能先完成,导致程序提前退出,会杀死之前所创建的线程。
多个线程实现
#include <iostream>
#include <thread>
static const int NUM_THREADS = 10; // 线程数量
using namespace std;
// 普通函数作为线程函数
void call_from_thread()
{
// 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
cout << "Hello from thread" << endl;
}
int main()
{
thread t[NUM_THREADS]; // 创建线程数组
for (int i = 0; i < NUM_THREADS; ++i)
{
t[i] = thread(call_from_thread); // 创建、启动线程
}
cout << "Hello from main thread" << endl;
for (int i = 0; i < NUM_THREADS; ++i)
{
t[i].join(); // 等待线程结束
}
return 0;
}
可以发现我们也可以在主线程之后做一些工作。
多个线程同时运送后没有特定的顺序,我们在编程时要确保线程不会修改临界资源,如果多个线程之间确实要竞争一些资源,我们需要编写更复杂的并行代码,使用类似nutex、atomic等方法避免上述问题。
线程传参的实现
#include <iostream>
#include <thread>
#include <string>
using namespace std;
// 普通函数作为线程函数
void call_from_thread(string& s)
{
// 为了防止输出乱序,实际项目中通常加锁,这里为了演示简单省略
cout << "Hello from thread :" << s << endl;
s = "hello"; //修改传入的参数
}
int main()
{
string str = "Jaylan";
thread t(&call_from_thread,std::ref(str)); // 创建线程,并传递参数,ref()表示显式的引用传递
t.join(); // 等待线程结
cout << "Hello from main thread=" << str << endl;
return 0;
}
先阻塞了主线程,等待线程结束,然后输出修改后的参数。
三、类成员函数作为线程函数的实现
在线程中如何将一个类成员函数作为线程函数呢?
成员方法作为线程来执行,有以下好处:
- 线程的变量传递会非常方便,直接读取成员变量即可。
- 线程体作为对象的一部分,可以访问对象的私有变量和方法。
成员函数作为线程函数(POSIX threads)
语法:int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*routine)(void*), void* arg);
主要第三个参数routine是一个普通函数,而不是一个类成员函数,这是为什么? 先测试一下:
class AA
{
public:
AA(const char* name);
void sayHelo*();
void stop();
private:
const char* m_name;
pthread_t m_thread; //线程
}
AA::AA(const char* name):m_name(name)
{
pthread_create(&m_thread,NULL,AA::sayHelo,this);
}
分析报错信息,成员函数默认是非静态函数,那么我们尝试将pthread_create的第三个参数改为静态函数。
static void* createThread(void* arg)
{
A* pa = (A*)arg;
pa->sayHello();
return (void*)0;
}
AA::AA(const char* name):m_name(name)
{
pthread_create(&m_thread,NULL,createThread,this);
}
我们通过static函数createThread作为第三个参数,同时使用第四个参数this线程传参,进而在createThread简间接调用class中的成员方法。 还有一种方法是:将routine函数声明为该类的友元函数,这样routine函数就可以直接调用该类的成员函数了。
还可以利用lambda函数,lambda函数可以捕获类成员变量,从而在lambda函数中调用类成员函数。
#include <iostream>
#include <pthread.h>
using namespace std;
class AA
{
public:
AA(const char* name);
void sayHelo();
void stop();
private:
const char* m_name;
pthread_t m_thread; //线程
};
AA::AA(const char* name):m_name(name)
{
auto f = [](void* arg)
{
printf("word thread\n");
AA* a = (AA*)arg;
a->sayHelo();
return (void*)0; //子线程退出,并返回一个空指针
};
pthread_create(&m_thread,NULL,f,this);
}
void AA::sayHelo()
{
printf("hello,%s\n",m_name);
}
void AA::stop()
{
pthread_join(m_thread,NULL); //主线程等待子线程结束
}
int main()
{
pthread_t tid;
AA a("张三");
printf("main thread\n");
a.stop();
return 0;
}
主线程和子线程的输出顺序是随机的,因为线程之间是竞争、无序的。
成员函数作为线程函数(C++11 threads)
#include #include #include using namespace std;
class AA { public: AA(const string& name); ~AA(); void sayHello(); void stop(); private: string m_name; thread m_thread; //线程 };
AA::AA(const string& name):m_name(name), m_thread(&AA::sayHello,this){}
AA::~AA() { if(m_thread.joinable())//判断线程是否可join { m_thread.join(); //主线程等待子线程结束 } }
void AA::sayHello() { cout << "hello," << m_name << endl; }
void AA::stop() { if(m_thread.joinable())//判断线程是否可join { m_thread.join(); //主线程等待子线程结束 } }
int main() { AA a("张三"); cout << "main thread.\n"; a.stop();
return 0;
}

文章进行了简单的线程实践,欢迎大家一起交流学习。