本次學習相關資料如下:
Boost C++ 庫 第 6 章 多線程(大部分代碼的來源)
Boost程序庫完全開發指南 - 深入C++“準”標準庫 第三版 羅劍鋒著
頭文件:
#include <stdio.h>
#include <string.h>
#include <boost\version.hpp>
#include <boost\config.hpp>
#include <iostream>
#include <boost\thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/cstdint.hpp>
#include <cstdlib>
#include <vector>
#include <random>
首先是如何創建簡單的多線程:
int sum = 0;void f_mutex(unsigned int no) {for (int i = 0; i < 10; i++) {sum++;std::cout << "我是線程" << no << ": sum 為" << sum << std::endl;}
}int main()
{std::cout << boost::thread::hardware_concurrency() << std::endl;//這個可以用來顯示CPU內核數,就是可以并行的線程數量std::cout << "程序開始" << std::endl;boost::thread a(f_metex,1); //第一個參數是線程要執行的函數,后面是函數所需要的參數boost::thread b(f_metex,2);a.join(); //等待線程結束,如果沒有這個的話主線程一下就結束了,子線程也沒了b.join();return 0;
}
上面的打印可能會出問題,sum的值不一定就是20,因為cpu在任意時刻都有可能切換線程,當1號線程讀取出sum的值,準備加1的時候,cpu可能會切換到2號線程,讀取出sum的值并加1,然后再切換回1號線程,這時候1號線程加1的值就不正確了。
所以要控制這個共享資源,出現了互斥量這樣的內容。
boost::mutex mu; //這個是互斥量void f_mutex(unsigned int no) {for (int i = 0; i < 10; i++) {mu.lock(); //上鎖了,這樣就算切換了線程,發現上鎖了也不能繼續往下執行sum++;std::cout << "我是線程" << no << ": sum 為" << sum << std::endl;mu.unlock(); //執行完后解鎖,這樣其他線程就可以執行了}
}
這個很好理解,因為有一個線程進入了臨界區,所以其他線程必須等待臨界區的訪問結束后才能進入,所以程序能夠正常打印sum為20
當然如果臨界區里面出現了異常,導致沒有正常解鎖,是不是就會影響到其他線程進入臨界區呢?所以我們可以使用lock_guard來解決問題。
std::default_random_engine e; //用來生成隨機數
/*
線程睡眠函數
*/
void wait(int seconds) {boost::this_thread::sleep(boost::posix_time::seconds(seconds));
}void f_lock_guard() {int i;for (i = 0; i < 10; i++) {boost::lock_guard<boost::mutex> lock(mu); wait(e() % 2); //這個是一個sleep函數,e()就可以生成一個隨機數了。sum++;std::cout << "我是線程" << boost::this_thread::get_id() << ": sum 為" << sum << std::endl; //這個get_id可以打印線程號,不需要傳入參數來區分線程了。 }std::cout << "我是線程" << boost::this_thread::get_id() << ",我結束了。i :" << i << std::endl;
}int main()
{std::cout << boost::thread::hardware_concurrency() << std::endl;//這個可以用來顯示CPU內核數,就是可以并行的線程數量std::cout << "程序開始" << std::endl;boost::thread a(f_lock_guard); //這里改動了,不再需要參數了boost::thread b(f_lock_guard);a.join(); //等待線程結束,如果沒有這個的話主線程一下就結束了,子線程也沒了b.join();return 0;
}
lock_guard 這個類會在構造函數里調用mu.lock(),析構函數里調用mu.unlock(),這樣的話lock就會在離開作用域的時候調用析構函數,從而調用mu.unlock(),退出臨界區的占用。
當程序存在多個線程需要讀取資源,而只有一個線程會修改資源(即讀者-寫者問題),使用上面的方法并不能很好地實現。
先來描述一下要求:
(1)可以存在多個線程同時讀取資源;
(2)讀取資源的時候不可以修改資源;
(3)修改資源的時候不可以讀取資源;
為了滿足上面3個要求,要增加新類型的鎖。
這里f_shared_fill往random_numbers添加隨機數,相當于寫著;
f_shared_print和f_shared_count 打印隨機數和統計隨機數,相當于讀者。
boost::shared_mutex shared_mu;
vector<int> random_numbers;int sum = 0;
void f_shared_fill() {for (int i = 0; i < 5; ++i) {boost::unique_lock<boost::shared_mutex> lock(shared_mu); //寫鎖std::cout << "我要寫入了" << std::endl;random_numbers.push_back(e()%100);std::cout << "我寫完了" << std::endl;lock.unlock();wait(1);}
}void f_shared_print() {for (int i = 0; i < 5; ++i) {wait(1);boost::shared_lock<boost::shared_mutex> lock(shared_mu); //讀鎖boost::this_thread::yield(); //放棄時間片,讓其他線程執行std::cout << "我在打印" << std::endl;std::cout << random_numbers.back() << std::endl;std::cout << "我打印完了" << std::endl;}
}void f_shared_count() {for (int i = 0; i < 5; ++i) {wait(1);boost::shared_lock<boost::shared_mutex> lock(shared_mu); //讀鎖boost::this_thread::yield(); //放棄時間片,讓其他線程執行std::cout << "我在統計" << std::endl;sum += random_numbers.back();std::cout << "我統計完了" << std::endl;}
}int main()
{std::cout << "程序開始" << std::endl;boost::thread a(f_shared_fill);boost::thread b(f_shared_print);boost::thread c(f_shared_count);a.join();b.join();c.join();std::cout << sum << std::endl;return 0;
}
注意寫鎖和讀鎖的區別。
boost::unique_lock和boot::lock_guard類似,也是能夠在構造函數中鎖定,析構函數中解鎖,但比boot::lock_guard更復雜。
f_shared_fill函數執行的時候上鎖,保證其他線程無法訪問。并且在后面有個主動調用lock.unlock的主動解鎖,然后進入睡眠,保證讀者線程能夠執行。
f_shared_print和f_shared_count開頭都有一個wait,用來保證f_shared_fill比他們先執行完。然后有一個釋放時間片yield,通過這個能夠更好地看出在f_shared_print或f_shared_count執行的過程中,cpu能夠執行另外的讀者線程,之所以不執行f_shared_fill是因為執行yield之前已經加了讀鎖。
程序是通過wait來控制線程的執行順序,不會出現讀線程執行了2次或以上而寫線程未執行或是寫線程寫入了2次或以上而讀線程一次都未執行。
當然可以用更好的方法來解決這個問題,那就是條件變量。
void f_condition_fill() {for (int i = 0; i < 10; ++i) {std::cout << "我是線程1 "<<random_numbers.size()<<std::endl;boost::unique_lock<boost::mutex> lock(mu);random_numbers.push_back(e()%100);cond.notify_all();cond.wait(mu);}
}void f_condition_print() {static int next_size = 1;for (int i = 0; i < 10; ++i) {std::cout << "我是線程2 " << random_numbers.size() << std::endl;boost::unique_lock<boost::mutex> lock(mu); while (next_size != random_numbers.size()) {cond.wait(mu);} std::cout << random_numbers.back() << std::endl;++next_size;cond.notify_all();}
}
int main()
{std::cout << "程序開始" << std::endl;boost::thread a(f_condition_fill);boost::thread b(f_condition_print);a.join();b.join();std::cout << sum << std::endl;return 0;
}
注意到這里用到的互斥量是mutex而不是shared_lock