笔者最近在项目中用到了多线程部分,其中可以使用POSIX pthreads和C++ threads,因此本文尝试对二者进行对照学习,之后通过提取项目中的一个demo进行实战。
参考文章:https://solarianprogrammer.com/2011/12/16/cpp-11-thread-tutorial/
https://www.kancloud.cn/wangshubo1989/new-characteristics/99709
在C++11引进多线程之前,我们不得不使用POSIX pthreads,因此本文主要包括三部分:
- POSIX多线程实践
- C++11 多线程实践
- 类成员函数作为线程函数的实现
一、POSIX多线程实践
此处参考: github threads
一个简单线程的实现
创建一个线程,该线程是joinable的,因此pthread_join阻塞,等待线程执行完毕。
//Create a Posix thread from the main program
#include <iostream>
#include <pthread.h>
//This function will be called from a thread
void *call_from_thread(void *) {
std::cout << "Launched by thread" << std::endl;
return NULL;
}
int main() {
pthread_t t;
//Launch a thread
pthread_create(&t, NULL, call_from_thread, NULL);
//Join the thread with the main thread
pthread_join(t, NULL);
return 0;
}
$ g++ posix_thread_00.cpp -o posix_thread_00 -lpthread
$ ./posix_thread_00
Launched by thread
多个线程的实现
//Create a group of Posix threads from the main program
#include <iostream>
#include <pthread.h>
static const int num_threads = 5;
//This function will be called from a thread
void *call_from_thread(void *) {
std::cout << "Launched by thread:"<<pthread_self()<<std::endl;
return NULL;
}
int main() {
pthread_t t[num_threads];
//Launch a group of threads
for (int i = 0; i < num_threads; ++i) {
pthread_create(&t[i], NULL, call_from_thread, NULL);
}
std::cout << "Launched from the main\n";
//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) {
pthread_join(t[i], NULL);
}
return 0;
}
$ g++ posix_thread_01.cpp -o posix_thread_01 -lpthread
$ ./posix_thread_01
Launched by thread:140300023379712
Launched by thread:140300014987008
Launched by thread:140300006594304
Launched by thread:140299998201600
Launched from the main
Launched by thread:140300031772416
当创建多个子线程时,外加主线程,共计6个线程,我们可以看到线程之间是平等竞争临界资源关系,顺序是无序的,因此如果要明确确定顺序,则需要同步等barrier术语来改进。
线程传参的实现
线程的传参可以是简单的数据类型,也可以是自定义的数据结构,因此我们在编写线程传参的时候要留意这些。
//Create a group of Posix threads from the main program
#include <iostream>
#include <pthread.h>
static const int num_threads = 5;
typedef struct {
int thread_id;
} thread_data;
//This function will be called from a thread
void *call_from_thread(void *args) {
thread_data *my_data = (thread_data *) args;
std::cout << "Launched by thread " << my_data->thread_id << std::endl;
return NULL;
}
int main() {
pthread_t t[num_threads];
thread_data td[num_threads];
//Launch a group of threads
for (int i = 0; i < num_threads; ++i) {
td[i].thread_id = i;
pthread_create(&t[i], NULL, call_from_thread, (void *) &td[i]);
}
std::cout << "Launched from the main\n";
//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) {
pthread_join(t[i], NULL);
}
return 0;
}
$ g++ posix_thread_02.cpp -o posix_thread_02 -lpthread
$ ./posix_thread_02
Launched by thread 0
Launched from the main
Launched by thread 1
Launched by thread 2
Launched by thread 4
Launched by thread 3
再一次验证了线程之间是竞争无序的。
二、C++ threads多线程实践
C++11增加了多线程支持,能够更好地在多核计算机上发挥性能。
一个简单线程的实现
编译时记得添加-std=c++11
选项
//Create a C++11 thread from the main program
#include <iostream>
#include <thread>
//This function will be called from a thread
void call_from_thread() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
//Launch a thread
std::thread t1(call_from_thread);
//Join the thread with the main thread
t1.join();
return 0;
}
//lambda格式
auto func = [] (string name) {
cout << "hello" << name << endl;
};
thread t(func,"tom");
t.join();
我们可以和上文使用POSIX线程API创建的线程比较,本质是一样的。
main函数创建了线程call_from_thread
,并在t1.join()
处完成。如果你忘记等待,main线程有可能先完成,此时程序退出时可能会杀死之前所创建的线程,而不管子线程是否已完成。
多个线程的实现
//Create a group of C++11 threads from the main program
#include <iostream>
#include <thread>
static const int num_threads = 10;
//This function will be called from a thread
void call_from_thread() {
std::cout << "Launched by thread\n";
}
int main() {
std::thread t[num_threads];
//Launch a group of threads
for (int i = 0; i < num_threads; ++i) {
t[i] = std::thread(call_from_thread);
}
std::cout << "Launched from the main\n";
//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) {
t[i].join();
}
return 0;
}
main函数也是一个线程,通常命名为主线程,因此上述代码实际运行了11个线程,我们也可以在主线程启动子线程后做一些工作。
多个线程同时运行后没有特定的顺序,我们在编程时要确保线程不会修改临界的资源。如果多个线程之间确实要竞争一些资源,我们需要编写更复杂的并行代码,使用类似mutex、atomic等方法避免上述问题。
线程传参的实现
#include <iostream>
#include <thread>
#include <string>
void thread_fun(std::string &str) {
std::cout << "thread fun";
std::cout << "msg is = " << str << std::endl;
str = "hello";
}
int main()
{
std::string str = "katty";
std::thread t(&thread_fun,std::ref(str));
std::cout<< "main thread = " << str << std::endl;
t.join();
return 0;
}
这里使用的是Linux g++编译器,使用了一些C++11新特性,大家根据自己的需要更改代码。
当然thread_fun也可以更改为lambdas 匿名函数实现。
四、类成员函数作为线程函数的实现
其实这次项目中真正遇到的是这个问题,如何将一个类成员函数作为一个线程函数实现呢?
很显然,因为成员方法可以作为线程体来执行,所以获得了如下好处:
- 线程的变量传递非常方便,直接读写成员变量即可;
- 线程体作为对象的一部分,可以访问对象的私有变量和方法。
在参考了文章:成员函数作为pthread线程
之后,我选择使用pthread和C++11 threads来实现之。
成员函数作为线程(POSIX threads)
我们再次复习下pthread的API接口:
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*routine)(void *),void *arg);
主要第三个参数routine是一个普通函数,而不能是一个类成员函数,这是为什么呢?
这里来测试下:
class A
{
public:
A(const char* name);
void sayHello();
void stop();
private:
const char* m_name ;
pthread_t m_thread;
};
A::A(const char*name)
{
m_name = name;
pthread_create(&m_thread,0,sayHello,this);
}
编译报错:
看到了吧:成员函数默认是非静态函数,我们这样做编译失败。
那么我们能否尝试将pthread_create的第三个参数改为静态函数呢?
static void* createThread(void *arg)
{
A *pa = (A*)arg;
pa -> sayHello();
return (void*)0;
}
A::A(const char*name)
{
m_name = name;
pthread_create(&m_thread,0,createThread,this);
}
可以看到,我们通过static函数createThread
作为第三个参数,同时使用第四个参数this
线程传参,进而在createThread
间接调用class中的成员方法,测试是可行的。
总结:常规方法是两种:
- 将routine声明为该类的友元函数
- 将routine声明为静态函数
那我们能否有其他的办法呢?
答案就是利用lambdas函数。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
class A
{
public:
A(const char* name);
void sayHello();
void stop();
private:
const char* m_name ;
pthread_t m_thread;
};
A::A(const char*name)
{
m_name = name;
//call class method
auto func = [] (void* arg)
{
printf("work thread\n");
A* a = (A*)arg;
a->sayHello();
return (void*)0;
};
pthread_create(&m_thread,0,func,this);
}
void A::sayHello()
{
printf("helo,%s\n",m_name);
}
void A::stop()
{
pthread_join(m_thread,0);
}
int main()
{
A a("paopao");
printf("main thread\n");
a.stop();
return 0;
}
不过一定要记得:这两个线程的执行顺序并不固定!
成员函数作为线程(C++11 threads)
既然都写到这里了,那就把C++11的API学习下吧:
//#include <pthread.h>
#include <iostream>
#include <thread>
#include <string>
using namespace std;
class A
{
public:
A(string& name);
void sayHello();
void stop();
private:
string m_name;
thread* m_thread;
};
A::A(string& name)
{
m_name = name;
//call class method
auto func = [] (void* arg)
{
std::cout << "work thread\n";
A* a = (A*)arg;
a->sayHello();
return (void*)0;
};
m_thread = new thread(func,this);
}
void A::sayHello()
{
std::cout << "hello," << m_name <<std::endl;
}
void A::stop()
{
m_thread->join();
}
int main()
{
string str = "paopao";
A a(str);
std::cout << "main thread\n";
a.stop();
return 0;
}
//
//g++ -std=c++11 thread_03.cpp -o thread_03 -lpthread
本篇文章大概也就是这样了,欢迎和大家一起探讨~