1. 程式人生 > >Posix執行緒和C++11多執行緒學習

Posix執行緒和C++11多執行緒學習

在C++11引進多執行緒之前,我們不得不使用POSIX pthreads,因此本文主要包括三部分:

  • POSIX多執行緒實踐
  • C++11 多執行緒實踐
  • 類成員函式作為執行緒函式的實現

一、POSIX多執行緒實踐

一個簡單執行緒的實現

建立一個執行緒,該執行緒是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中的成員方法,測試是可行的。

那我們能否有其他的辦法呢? 答案就是利用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

本篇文章大概也就是這樣了,歡迎和大家一起探討~