1. 程式人生 > >C++多執行緒庫筆記1

C++多執行緒庫筆記1

來源:C++ 11 多執行緒

課程介紹

併發:同一時間間隔
並行:同一時刻
多程序:程序間通訊:檔案、管道、訊息佇列
多程序:共享記憶體
C++多執行緒庫<thread>
建立一個執行緒

thread t(callable);

其中callable為可呼叫物件

void greeting() {
	cout << "hello multithread" << endl;
}
int main() {
	thread t(greeting);
	t.join();
	return 0;
}

t.join()表示主執行緒等待子執行緒結束後執行
在這裡插入圖片描述


如果主執行緒不等待子執行緒結束,使用detach

void greeting() {
	cout << "hello multithread" << endl;
}
int main() {
	thread t(greeting);
	t.detach();
	return 0;
}

由於主執行緒執行太快,子執行緒還沒有執行,主執行緒就已經退出
在這裡插入圖片描述
不能同時對一個執行緒呼叫detach()join()

void greeting() {
	cout << "hello multithread" << endl;
}
int main
() { thread t(greeting); t.join(); t.detach(); return 0; }

在這裡插入圖片描述
可以使用函式判斷能否join

int main() {
	thread t(greeting);
	if (t.joinable())
		t.join();
	return 0;
}

執行緒管理

	// 考慮下面的例子
void greeting() {
	cout << "hello multithread" << endl;
}
int main() {
	thread t(greeting);
	for(int i=0;i<
100;++i){ cout<<"from main: "<< i <<endl; } t.join(); return 0; }

在這裡插入圖片描述
上面的程式碼,如果for迴圈中丟擲異常,在joint就會被銷燬,可以修改為

void greeting() {
	cout << "hello multithread" << endl;
}
int main() {
	thread t(greeting);
	try {
		for (int i = 0; i<100; ++i) {
			cout << "from main: " << i << endl;
		}
	}
	catch (...) {
		t.join();
		throw;
	}
	t.join();
	return 0;
}

這樣,即使丟擲了異常,也會呼叫join

可以通過可呼叫物件建立執行緒

public:
	void operator()(string& msg) {
		for (int i = 0; i < 1; ++i)
			cout << "from t1: " << msg << endl;
		msg = "I love HouZongliang";
	}
};

int main() {
	string s = "I love Houzi";
	thread t((Fator()), s);
	t.join();
	cout << s << endl;
	return 0;
}

在這裡插入圖片描述
上面的形參是引用,但我們發現,儘管修改了msg,但是實參並沒有被修改。如果要修改引數的話,需要這樣傳參。

class Fator {
public:
	void operator()(string& msg) {
		for (int i = 0; i < 1; ++i)
			cout << "from t1: " << msg << endl;
		msg = "I love Cpp";
	}
};

int main() {
	string s = "I love Houzi";
	thread t((Fator()), ref(s));
	t.join();
	cout << s << endl;
	return 0;
}

在這裡插入圖片描述
如果主執行緒中不需要再使用s,可以用move把s變成右值

class Fator {
public:
	void operator()(string& msg) {
		for (int i = 0; i < 1; ++i)
			cout << "from t1: " << msg << endl;
		msg = "I love Cpp";
	}
};

int main() {
	string s = "I love Houzi";
	thread t((Fator()), move(s));
	t.join();
	cout << s << endl;
	return 0;
}

在這裡插入圖片描述
可以發現s已經是空值了。
標準庫中有很多不能複製,只能移動的型別,比如thread

	//下面的會報錯
	//thread t1 = t;
	thread t1 = move(t);

下面的函式給出了最大可建立的執行緒數

int main() {
	//string s = "I love Houzi";
	//thread t((Fator()), move(s));
	//t.join();
	cout << std::thread::hardware_concurrency() << endl;
	return 0;
}

在這裡插入圖片描述

資料競爭與互斥物件

下面的程式碼,打印出來的結果往往是亂的

void greeting() {
	for(int i=100;i>0;--i)
		std::cout << "from t1: "<< i << std::endl;
	return;
}

int main() {
	thread t(greeting);
	for (int i = 100; i>0; --i)
		std::cout << "from main" << i << std::endl;
	t.join();
	return 0;
}

在這裡插入圖片描述
原因是不同的執行緒競爭資源cout。
可以使用互斥物件來同步資源的訪問。

#include <mutex>

std::mutex mu;

void shared_print(string msg, int id) {
	mu.lock();
	cout << msg << " " << id << endl;
	mu.unlock();
}
void greeting() {
	for(int i=100;i>0;--i)
		shared_print("from t1: ", i);
	return;
}

int main() {
	thread t(greeting);
	for (int i = 100; i>0; --i)
		shared_print("from main", i);
	t.join();
	return 0;
}


在這裡插入圖片描述
但是cout << msg << " " << id << endl;可能會丟擲異常。使用類lock_guard<mutex>來管理互斥鎖

void shared_print(string msg, int id) {
	//mu.lock();
	lock_guard<mutex> guard(mu);
	cout << msg << " " << id << endl;
	//mu.unlock();
}

guard析構的時候,鎖會被釋放。
這個執行緒還有一個問題,cout是一個全域性的變數。其他函式可以在不加鎖的情況下使用cout。

class LofFile {
	mutex m_mutex;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		lock_guard<mutex> guard(m_mutex);
		f << "From " << id << ": " << val << endl;
	}
};
void greeting(LofFile& log) {
	for(int i=100;i>0;--i)
		log.shared_print("from t1: ", i);
	return;
}

int main() {
	LofFile log;
	thread t(greeting,ref(log));
	for (int i = 100; i>0; --i)
		log.shared_print("from main", i);
	t.join();
	return 0;
}

在這裡插入圖片描述
上面的例子中,f始終在mutex的保護之內,但是我們不能把f返回給類外,或者把f傳給其他引數,比如

ofstream& GetStream() { return f; }

或者

void processf(void fun(ofstream&)){
	fun(f);
}

死鎖

假設有下面的程式碼

class LofFile {
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		lock_guard<mutex> guard(m_mutex);
		lock_guard<mutex> guard2(m_mutex2);
		cout << "From " << id << ": " << val << endl;
	}
	void shared_print2(string id, int val) {
		lock_guard<mutex> guard2(m_mutex2);
		lock_guard<mutex> guard(m_mutex);
		cout << "From " << id << ": " << val << endl;
	}
};

void greeting(LofFile& log) {
	for(int i=100;i>0;--i)
		log.shared_print("t1: ", i);
	return;
}

int main() {
	LofFile log;
	thread t(greeting,ref(log));
	for (int i = 100; i>0; --i)
		log.shared_print2("main", i);
	t.join();
	return 0;
}

執行結果:
在這裡插入圖片描述
避免方式:

  1. 申請資源順序相同
class LofFile {
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		lock_guard<mutex> guard(m_mutex);
		lock_guard<mutex> guard2(m_mutex2);
		cout << "From " << id << ": " << val << endl;
	}
	void shared_print2(string id, int val) {
		lock_guard<mutex> guard(m_mutex);
		lock_guard<mutex> guard2(m_mutex2);
		cout << "From " << id << ": " << val << endl;
	}
};

void greeting(LofFile& log) {
	for(int i=100;i>0;--i)
		log.shared_print("t1: ", i);
	return;
}

int main() {
	LofFile log;
	thread t(greeting,ref(log));
	for (int i = 100; i>0; --i)
		log.shared_print2("main", i);
	t.join();
	return 0;
}

  1. 使用C++提供的lock
    此時adopt_lock是為了告訴lock_guard已經鎖了,只需要獲取試用權,並釋放
class LofFile {
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		lock(m_mutex, m_mutex2);
		lock_guard<mutex> guard(m_mutex,std::adopt_lock);
		lock_guard<mutex> guard2(m_mutex2, std::adopt_lock);
		cout << "From " << id << ": " << val << endl;
	}
	void shared_print2(string id, int val) {
		lock(m_mutex, m_mutex2);
		lock_guard<mutex> guard2(m_mutex2, std::adopt_lock);
		lock_guard<mutex> guard(m_mutex, std::adopt_lock);
		cout << "From " << id << ": " << val << endl;
	}
};

void greeting(LofFile& log) {
	for(int i=100;i>0;--i)
		log.shared_print("t1: ", i);
	return;
}

int main() {
	LofFile log;
	thread t(greeting,ref(log));
	for (int i = 100; i>0; --i)
		log.shared_print2("main", i);
	t.join();
	return 0;
}
  1. 避免使用多個鎖

Unique Lock和Lazy Initialization

另一種加鎖的方式

class LofFile {
	mutex m_mutex;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		std::unique_lock<mutex> locker(m_mutex);
		cout << "From " << id << ": " << val << endl;
		locker.unlock();
		//後面的操作不需要加鎖
	}
};

void greeting(LofFile& log) {
	for(int i=100;i>0;--i)
		log.shared_print("t1: ", i);
	return;
}

int main() {
	LofFile log;
	thread t(greeting,ref(log));
	for (int i = 100; i>0; --i)
		log.shared_print("main", i);
	t.join();
	return 0;
}

unique_lock還有其他靈活的操作

class LofFile {
	mutex m_mutex;
	ofstream f;
public:
	LofFile() {
		f.open("log.txt");
	}
	void shared_print(string id, int val) {
		std::unique_lock<mutex> locker(m_mutex,std::defer_lock);
		//...目前還沒有加鎖
		locker.lock();
		cout << "From " << id << ": " << val << endl;
		locker.unlock();
		//後面的操作不需要加鎖
	}
};

void greeting(LofFile& log)