Posix线程和C++11多线程学习

笔者最近在项目中用到了多线程部分,其中可以使用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 
#include 

//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 
#include 

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 
#include 
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 
#include 

//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 
#include 

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 
#include 
#include 

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中的成员方法,测试是可行的。

总结:常规方法是两种:

  1. 将routine声明为该类的友元函数
  2. 将routine声明为静态函数

那我们能否有其他的办法呢?
答案就是利用lambdas函数。

#include 
#include 
#include 

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 
#include 
#include 
#include 
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

本篇文章大概也就是这样了,欢迎和大家一起探讨~

你可能感兴趣的:(Linux,C学习)