1、類型與變量相關
1.1、nullptr:
取代了NULL,專用於空指針
1.2、constexpr:
近似const, 可以修飾變量,也可以修飾函數,
修飾變量如:
const int global = 100;
int main () {
int temp = 100;
constexpr int a = 1; //right
constexpr int b = global; //right
constexpr int c = temp; //wrong
}
既可以賦值字面常量也可以賦值以const變量
重點:constexpr修飾的函數,生效於編譯時而不是運行時, 重點應用於修飾函數使其在編譯期大幅度被解釋
被constexpr修飾的函數,無論是普通函數,還是類成員函數,必須是編譯器可計算得到結果,即字面常量,不可是運行時才能獲取的內容
例1:
constexpr int calc_in_compile_0 () {
return 100;
}
constexpr int calc_in_compile_1 (int a) {
return a * 100;
}
constexpr int calc_in_compile_2 (int b, int c) {
return c * calc_in_compile_1(b);
}
EXPECT_EQ(100, calc_in_compile_0());
constexpr int a = 1;
EXPECT_EQ(100, calc_in_compile_1(a));
EXPECT_EQ(10000, calc_in_compile_2(a, calc_in_compile_1(a)));
例2:
代替了"const _max = INT_MAX"
static constexpr int max () {
return INT_MAX;
}
static constexpr int min () {
return INT_MIN;
}
constexpr int _max = max(), _min = min();
例3:
class Calc {
double a_;
public:
/*構造函數在這裏,必須用constexpr修飾,因爲類成員函數是用constexpr修飾的*/
constexpr Calc(double a):a_(a) {}
constexpr double GetFabs() const {
return std::fabs(a_);
}
constexpr double GetAbs() const {
return std::abs(a_);
}
constexpr double GetSquare() const {
return a_ * a_;
}
};
constexpr Calc calc(5.1);
constexpr double _fabs = calc.GetFabs();
///_fabs = 10.0;
LOG(INFO) << "fabs: " << _fabs;
double _abs = calc.GetAbs();
LOG(INFO) << "abs: " << _abs;
_abs = 10.0;
LOG(INFO) << "abs: " << _abs;
double _square = calc.GetSquare();
LOG(INFO) << "square: " << _square;
_square = 10.0;
LOG(INFO) << "square: " << _square;
1.3、using取代typedef:
typedef double db; //c99
using db = double; //c++11
typedef void(*function)(int, int);//c99,函數指針類型定義
using function = void(*)(int, int);//c++11,函數指針類型定義
using kvpairs = std::map<std::string, std::string>; //c++11
using CompareOperator = std::function<int (kvpairs &, kvpairs &)>; //c++11
using query_record = std::tuple<time_t, std::string>; //c++11
template<class T> using twins = std::pair<T, T>; //更廣泛的還可以用於模板
1.4、auto & decltype:
auto讓編譯器通過初始值來推算變量的類型。當然,其定義的變量必須要有初始值
auto a = 1;
auto task = std::function<void ()>([this, a] {
................
});
decltype(變量)可以獲取變量的類型
auto a = 1;
decltype(a) b = 2;
decltype(b) c = add(a, b);
注意下,decltype((a) )的結果是引用,此時創建新的變量就將會報錯,或者說:
int &b = a;
decltype(b) c;//也報錯,因爲b是a的引用,decltype(b)就會報錯,效果同decltype((a))
此外,auto在容器的迭代器的使用,大大降低了代碼開發量
對於vector、map、set等容器
for (auto i: V) {
......
}
1.5、字符串和數值類型的轉換
以前的atoi、itoa等等成爲歷史
to_string:itoa成爲歷史
stoi、stol、stoul、stoll、stoull、stof、stod、stold:atoX成爲歷史
1.5、random_device
生成隨機數,免去了以前需要自行調用srand初始化種子的步驟,因爲有時忘了初始化結果導致錯誤。用法:
std::random_device rd;
int randint = rd();
1.6、std::ref和std::cref
分別對應變量的引用和const引用,主要用於作爲c++11函數式編程時傳遞的參數
1.7、std::chrono時間相關
比以前的時間方便了許多:
std::chrono::duration<double> duration //時間間隔
std::this_thread::sleep_for(duration); //sleep
LOG(INFO) << "duration is " << duration.count() << std::endl;
std::chrono::microseconds //微秒
std::chrono::seconds //秒
end = std::chrono::system_clock::now(); //獲取當前時間
1.8、原子變量
std::atomic<XXX>
用於多線程資源互斥操作,屬c++11重大提升,多線程原子操作簡單了許多
事實上基於c++11實現的無鎖隊列,讓boost::lockfree無鎖隊列也將成爲歷史
1.9、正則表達式std::regex
噁心的C正則(regex.h)和boost正則成爲歷史
1.10、編譯期斷言static_assert
static_assert是用於涉及模板的assert,編譯期就能發現不滿足的情況,無需等到運行時出現core
如下最後一個被注掉的static_assert如果放開,則無法通過編譯。
template<class T> class C {
T data1_;
int data2_;
public:
C(T data1, int data2):data1_(data1), data2_(data2) {
/*if the condition is not satisfiedm, would be errored by compiler in compling*/
//static_assert(sizeof(T) > 4, "sizeof(T) is not larger than 4");
static_assert(sizeof(T) >= 4, "sizeof(T) is not larger than 4");
//static_assert(data2_ >= 10, "could not use static_assert here! condition must could be calced in compling!");
}
};
TEST(test_static_assert, test) {
C<double> c(1.1, 1);
}
2、容器
2.1、tuple & 花括號初始化
元組的出現,和python拉齊了,c++也實現了函數可以多個返回值
using result = std::tuple<int, char, double>;
result res = {1,'a',1.0};
return res;
return {2, 'b',100.0};
std::vector<int> v{1, 2, 3, 4, 5};
using res_tp = std::tuple<bool, char, int, float, double>;
res_tp res(true, 'b', 11, 1.1, 100.1);
LOG(INFO) << "res.bool: " << std::get<0>(res);
LOG(INFO) << "res.char: " << std::get<1>(res);
LOG(INFO) << "res.int: " << std::get<2>(res);
LOG(INFO) << "res.float: " << std::get<3>(res);
LOG(INFO) << "res.double: " << std::get<4>(res);
以上都是合法的,尤其對vector,簡單的測試程序再不需要一行行的push_back了!
2.2、hash正式進入stl
unordered_map、unordered_set、unordered_multimap、unordered_multiset。extstl擴展方式的使用hash成爲歷史。
2.3、emplace:
作用於容器,區別於push、insert等,如push_back是在容器尾部追加一個容器類型對象,emplace_back是構造1個新對象並追加在容器尾部
對於標準類型沒有變化,如std:;vector<int>,push_back和emplace_back效果一樣
如自定義類型class A,A的構造函數接收一個int型參數,
那麼對於push_back需要是:
std::vector<A> vec;
A a(10);
vec.push_back(a);
對於emplace_back則是:
std::vector<A> vec;
vec.emplace_back(10);
改進點是什麼?改進點:避免無用臨時變量。比如上面例子中的那個a變量
2.4、shrink_to_fit
這個改進還是有點意義的,日常程序應該能減少不少無意義的內存空間佔用
push、insert這類操作會觸發容器的capacity,即預留內存的擴大,實際開發時往往這些擴大的區域並沒有用途
std::vector<int> v{1, 2, 3, 4, 5};
v.push_back(1);
std::cout << "before shrink_to_fit: " << v.capacity() << std::endl;
v.shrink_to_fit();
std::cout << "after shrink_to_fit: " << v.capacity() << std::endl;
可以試一試,減少了很多,有一定價值3、對於類
3.1、構造函數
3.1.1、控制構造函數
1、default關鍵字生成默認構造函數和析構函數
default的默認構造方式可生成:默認無參數的構造函數、拷貝構造函數、賦值構造函數
析構函數同樣可以由default默認構造
class A11 {
int data;
public:
A11() = default;
~A11() = default;
A11 (int _data):data(_data) {}
};
void c11_construct () {
A11 a;
A11 b(a);
A11 c;
c = b;
A11 d(1);
}
2、delete關鍵字禁止拷貝構造、禁止賦值構造、禁止自定義參數的構造函數
注意析構函數不可由delete修飾
c++11以前的方式,是把需要禁止的構造函數,放在private裏使外部無法調用;
c++11風格的禁止構造的noncopyable基類實現如下,禁止了拷貝構造和賦值構造:
class noncopyable {
protected:
constexpr noncopyable() = default;
~noncopyable() = default;
noncopyable(const noncopyable &) = delete;
noncopyable &operator= (const noncopyable &) = delete;
};
3、委託構造函數
一個構造函數,使用自己的參數,傳遞給其他構造函數去構造,作爲自己的構造函數實現,
如下例,後面兩個構造函數,均傳遞參數,委託給第一個構造函數去實現
struct A {
bool a_;
char b_;
int c_;
float d_;
double e_;
A(bool a, char b, int c, float d, double e): a_(a), b_(b), c_(c), d_(d), e_(e) {}
//construct reuse
A (int c): A(true, 'b', c, 1.1, 1000.1) {}
A (double e): A (false, 'a', 0, 0.1, e) {}
};
A o1(10);
LOG(INFO) << "a: " << o1.a_ << ", b: " << o1.b_ << ", c: " << o1.c_ << ", d: " << o1.d_ << ", e: " << o1.e_;
A o2(5.5);
LOG(INFO) << "a: " << o2.a_ << ", b: " << o2.b_ << ", c: " << o2.c_ << ", d: " << o2.d_ << ", e: " << o2.e_;
4、移動構造函數:
屬於c++11的右值引用的衍生效果之一,首先描述右值引用std::move
std::move主要能解決的拷貝性能問題
類似於python的深拷貝和淺拷貝, python中的對象賦值和copy.copy都是淺拷貝, 賦值的都是對象的引用, copy.deepcopy則是深拷貝
首先插一段python代碼幫助理解深淺拷貝,建議用pdb跟一下代碼感受更加深刻:
import copy
import json
a = [1, 2, 3, 4, 5, [99, 98]]
#b全都是a的引用
b = a
#c的非子對象都是a的複製構造, 但子對象還是引用
c = copy.copy(a)
#d全都是a的複製構造
d = copy.deepcopy(a)
print "a append a new element 100"
a.append(100)
print "a: %s" % json.dumps(a)
print "b = a, b will change: %s" % json.dumps(b)
print "c = copy.copy(a): %s" % json.dumps(c)
print "d = copy.deepcopy(a): %s" % json.dumps(d)
print "a's subobject append a new element 100"
a[5].append(100)
print "a: %s" % json.dumps(a)
print "b = a, b will change: %s" % json.dumps(b)
print "c = copy.copy(a), will change: %s" % json.dumps(c)
print "d = copy.deepcopy(a): %s" % json.dumps(d)
c++11的std::move, 解決的問題是一個複製效率的問題::
對臨時變量(如函數中的參數)的複製,通過更改對象的所有者(move),實現免內存搬遷或拷貝(去除深拷貝),
提高"複製"效率(其實不是複製,僅是更改了對象的所有者。
例一:改變引用持有者(減少複製成本,移交引用權力給有用的變量,同時免除不再有用變量對引用的持有權)
std::string a = "123"; //或std::string &&a = "123";顯示的標識a是全局字符串"123"的右值引用
LOG(INFO) << "at first, std::string a is: " << a; //打印123
/*右值"123", 它的所有者將從原先的左值(變量std::string a), 轉移到新的左值(std::vector v)
*所以, 使用std::move時一定保證, 以前的左值不再真需要了. 典型使用場合就是: (構造)函數的參數, 避免了再複製*/
v.push_back(std::move(a));
LOG(INFO) << "after std::move(a), now std::string a is: " << a; //打印空
最後的glog將無法打印出a最開始的拷貝構造獲取的值"123",因爲全局字符串"123"的所有者,已經從最開始的變量a,轉移到了v
這在日常場合也是需要的,用途爲:
1、減少內存複製成本
2、將不再需要的變量,取消它對原先持有變量(內存)的持有(修改)權限
例二:移動構造函數
class test {
public:
std::vector<std::string> t_;
test(std::vector<std::string> &tmp) {
for (auto& i: tmp) {
//not copy rvalue to t_, only add rvalue reference to t_ and update rvalue's lifecycle
t_.push_back(std::move(i));
}
}
};
/*起初, 右值("123", "456", "789", "012", "345")都歸屬於左值temp*/
std::vector<std::string> temp = {"123", "456", "789", "012", "345"};
LOG(INFO) << "before move to object ot, t's size is: " << temp.size();
for (auto& i: temp) {
LOG(INFO) << " OLD LVALUE(object temp) element: " << i;
}
/*由類test的構造函數, 更改右值的所有者爲類test的對象ot*/
test ot(temp);
LOG(INFO) << "after move elements of temp to object ot, now ot's size is: " << ot.t_.size();
for (auto& i: temp) {
LOG(INFO) << " OLD LVALUE(object temp) element: " << i;
}
for (auto& i: ot.t_) {
LOG(INFO) << " NEW LVALUE(object ot) element: " << i;
}
第一輪glog, vector容器temp可以打印出其持有的全局字符串列表
例三:c++11風格的新老容器的數據移交:
如果一個老容器如vector容器oldv,需要將其內部數據複製給新容器如vector容器newv,且老容器後面無用,數據量很大;
那麼c++11的std::make_move_iterator將派上用場,它可以將一個普通迭代器,如oldv.begin(),轉化爲"move式迭代器",配合std::copy,將老容器內全部數據的引用,move給新容器同時取消老容器對數據的持有權。這就是c++11風格的高速數據拷貝方式。
std::vector<std::string> oldv = {"123", "456", "789"};
std::vector<std::string> newv(oldv.size());
for (auto &i: oldv) {
std::cout << i << "\t";
}
std::cout << std::endl;
std::copy(std::make_move_iterator(oldv.begin()), std::make_move_iterator(oldv.end()), newv.begin()); //c++11做法,move引用
//std::copy(oldv.begin(), oldv.end(), newv.begin()); //傳統做法,複製
for (auto &i: oldv) {
std::cout << i << "\t";
}
std::cout << std::endl;
for (auto &i: newv) {
std::cout << i << "\t";
}
std::cout << std::endl;
第一次打印:老容器正常打印
第二次打印:老容器無法打印了,因爲每個
第三次打印:新容器正常打印
關於右值引用是c++11的一大重點,還有很多其他相關內容,個人認爲理解和運用到這裏基本可滿足了。
5、繼承構造函數
回到c++11的關於類的構造問題,近似於委託構造函數原理,如下:
struct A {
int a;
A(int _a):a(_a + 100){}
};
struct B : public A {
int b;
B(int _b):A(_b), b(_b + 10000){}
};
B obj(1);
std::cout << obj.a << ", " << obj.b << std::endl;
3.2、override和final
作用於虛函數,更多的作用是:顯式的標識是否應該多態繼承或不應該
1、override:子類用override修飾其虛函數,表示要多態繼承基類的虛函數。不可以修飾非虛函數
舉一個rocksdb的merge運算符重載的例子:
class ProcessMerge : public rocksdb::MergeOperator {
public:
virtual bool FullMergeV2 (const MergeOperationInput &merge_in,
MergeOperationOutput *merge_out) const override {
merge_out->new_value.clear();
if (merge_in.existing_value != nullptr) {
merge_out->new_value.assign(merge_in.existing_value->data(), merge_in.existing_value->size());
}
for (const rocksdb::Slice& m : merge_in.operand_list) {
merge_out->new_value.append("|");
merge_out->new_value.append(m.data(), m.size());
}
return true;
}
const char* Name() const override { return "ProcessMerge"; }
};
2、final:基類用final修飾其虛函數,意外其子類不可以多態繼承該虛函數
class father {
public:
int a_;
int GetA() {return a_;}
virtual void SetA(int a) {
a_ = a;
LOG(INFO) << "father modify a to " << a_;
}
//add keyword final to avoid non-anticipated inherit in compling but not errored in running
//virtual void SetA(int a) final {a_ = a;}
public:
father(int a):a_(a) {}
};
class Son: public father {
int b_;
public:
Son(int a, int b):father(a), b_(b) {}
//add keyword override to avoid the error in compling but not errored in running.(eg. 'int SetA(double a){...} override' woule be errored by compiler)
virtual void SetA(int a) override {
a_ = a;
LOG(INFO) << "son modify a to " << a_;
}
//virtual void SetA(double a) override {a_ = a;}
};
如father基類的SetA實現爲"virtual void SetA(int a) final {a_ = a;}",則子類Son再多態繼承實現SetA方法就會報錯了。
3.3、建議:
構造與析構:全部的複製構造、賦值構造、所有權移動構造、自定義構造函數,以及全部的複製運算符、賦值運算符、所有權移動運算符,儘可能自行全部都實現
繼承:子類的虛函數多態實現要加override顯式的表明,不讓子類多態實現的虛函數也要記得加入final;
宗旨:讓c++11的編譯器更多的幫助發現問題
4、lambda、bind、function:
函數式編程是c++11重要亮點之一
4.1、直接lambda表達式
完全如同python
int a = 1, b = 2;
auto multi = [](int a, int b){
b = a + a + a;
return a + b;
};
LOG(INFO) << "by lambda: " << multi(a, b);
函數multi
4.2、c++11風格的函數指針std::function & std::bind
int func1 (int a, int b) {
b = a + a + a;
return a + b;
}
auto a = 1, b = 2;
std::function<int (int, int)> modify_add0(func1);
LOG(INFO) << "directly assign function: " << modify_add0(a, b);
通過指定返回值、參數列表、綁定的函數和函數名,定義一個函數(指針)modify_add0
綁定的函數,可以是普通函數,也可以是類成員函數,同時指定:
class ca {
public:
bool func(int a) {
LOG(INFO) << "aaa: " << a;
}
};
ca o;
std::function<bool (int)> f = std::bind(&ca::func, o, std::placeholders::_1);
f(1);
原先只有在boost出現且極爲受限的函數佔位符,也加入到了標準庫,即std::placeholders,傳遞自定義參數綁定類成員函數時,需要配合使用std:bind。
bind和placeholders,同樣可以用於普通函數:
int func1 (int a, int b) {
b = a + a + a;
return a + b;
}
auto a = 1, b = 2;
auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);
LOG(INFO) << "directly run auto: " << auto1(a, b);
auto可以自動識別標準類型的變量的類型,同樣可以用於std:;function:int func1 (int a, int b) {
b = a + a + a;
return a + b;
}
auto a = 1, b = 2;
auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);
LOG(INFO) << "directly run auto: " << auto1(a, b);
std:;function作爲函數指針,同樣可以作爲參數傳遞並執行:
int func1 (int a, int b) {
b = a + a + a;
return a + b;
}
int func3 (auto f) {
return f(1, 2);
}
auto a = 1, b = 2;
auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);
LOG(INFO) << "run auto in function: " << func3(auto1);
bind內不僅不再有boost佔位符實現的1st、2nd的個數限制,還可以傳遞常量,並可以指定參數的順序:
int func2 (int a, double b, std::string c) {
b = a + a + a;
return int(a + b);
}
/*std::function內的定義了該function調用時的順序, 也是_1、_2、..._n的順序, bind內要整理符合綁定的函數參數順序*/
std::function<int (std::string, int)> modify_add2 = std::bind(func2, std::placeholders::_2, 2.0, std::placeholders::_1);
LOG(INFO) << "by bind with partly arg: " << modify_add2("aaa", 1);
modify_add2函數執行時,第一個參數"aaa"第二個參數1,貌似和綁定的函數func2的順序不符,就是因爲bind內指定了佔位符標識,佔位符2作爲第一個參數,常量2.0作爲第二個參數,佔位符1作爲第三個參數,即1、2.0、"aaa"
更廣泛的用法,直接定義函數體:
std::function<int ()> modify_add3 = std::function<int ()>([=, &b]{
b = a + a + a;
return a + b;
});
LOG(INFO) << "directly in-function: " << modify_add3();
這個做法是後面描述的std::thread的典型適配方法,讓static void thread_func(void *arg) {......}作爲線程執行函數體的作法成爲歷史!
對於函數參數爲引用、常引用、指針的方法:
int func4 (const int &a, int &b) {
b = 3;
return a + b;
}
int func5 (int *a) {
return *a;
}
std::function<int (const int&, int&)> modify_add4 = std::bind(func4, std::placeholders::_1, std::placeholders::_2);
LOG(INFO) << "args is const reference and reference: " << modify_add4(std::cref(a), std::ref(a));
std::function<int (int *)> modify_add5 = std::bind(func5, std::placeholders::_1);
LOG(INFO) << "args is const reference and reference: " << modify_add5(&a);
在這裏,std::ref、std::cref派上了用場
5、動態指針
這也是c++11一個重要亮點
如同函數式編程,動態指針同樣大量移植了原先boost裏的東西
5.1、unique_ptr
功能基本對應boost的scoped_ptr,或之前stl的auto_ptr,生命週期隨構造者,reset自動析構再重新構造,get判斷是否有效、支持放在容器內;
真正意義智能指針。
不論是臨時變量指針、類成員指針變量.....90%的指針都應該用這個
5.2、shared_ptr
功能對於boost的shared_ptr,可以有多個持有者的共享指針,即所謂引用計數型指針,直到最後一個持有者delete釋放時,其指向的資源纔會真正被釋放
典型應用案例:如對同一個全局無鎖隊列對象由shared_ptr封裝,多線程的多個持有者均持有對其的引用。直到全部線程都釋放掉對其的引用時,該無鎖隊列對象纔會被最終銷燬。
也就是shared_ptr適合用於管理“全局動態資源”
6、多線程與互斥同步(互斥鎖,條件變量)
這也是c++11的一個重要亮點
c++11的多線程管理瞬間變得和boost甚至比boost的還要方便:
static void *ThreadFunc(void *arg) {
reinterpret_cast<ThreadPool *>(arg)->process();
return 0;
}
int a = new int;
std::thread th(&ThreadFunc, (void *)&a);
一個線程池的構造:ThreadPool::ThreadPool (int thread_num): thread_num_(thread_num),
pending_num_(0),
running_num_(0),
task_count_(0),
stop_(true) {
Start();
}
ThreadPool::~ThreadPool () {
Stop(false);
}
bool ThreadPool::Start () {
std::unique_lock<std::mutex> lock(mtx_);
stop_ = false;
for (auto i: common::Range(0, thread_num_)) {
ths_.push_back(std::thread(&ThreadFunc, this));
}
}
就是這樣創建並運行
結合前邊的std::function,可以讓static void ThreadFunc(void *arg)成爲歷史:
std::unique_ptr<std::thread> agent_;
agent_.reset(new std::thread([this] () {
while (1) {
std::unique_lock<std::mutex> lock(mtx_);
if (!lockfreequeue_.get() || (lockfreequeue_->empty() && !stop_)) {
std::cv_status cvsts = cond_.wait_for(lock, std::chrono::milliseconds(100));
if (cvsts == std::cv_status::timeout) {
continue;
}
}
if (stop_) {
break;
}
void *msg = nullptr;
lockfreequeue_->pop(msg);
if (msg) {
Task task = std::bind(&DataPreProcess::PreProcess, this, msg);
workers_->AddTask(task);
}
}
LOG(INFO) << "agent thread exit.";
}));
即,直接定義函數體。在c++11直接定義函數體代替靜態函數是非常常用的方式。
提到多線程,不能不提到多線程互斥與同步,c++11在這方面同樣大量移植boost:
std:;mutex
std::unique_lock
std::condition_variable
它們讓多線程共用全局posix互斥鎖、條件變量的方式成爲歷史
std::unique_lock和std::condition_variable,基本對應boost的scoped_lock和condition_variable,使用方法完全一樣
以線程池的部分實現爲例:
1、首先聲明和定義線程池的執行實體:
using Task = std::function<void ()>;
struct Timertask {
bool flag_;
Task task_;
int64_t timeval_;
int64_t exec_time_;
bool operator< (const struct Timertask otherone) const {
return exec_time_ > otherone.exec_time_;
}
Timertask(const Task &task, int64_t timeval, int64_t exec_time, bool flag = false):flag_(flag), task_(task), timeval_(timeval), exec_time_(exec_time) {}
Timertask(const Task &task, int64_t timeval, bool flag = false):flag_(flag), task_(task), timeval_(timeval) {
int64_t nowtime = common::getime_micros();
exec_time_ = timeval_ + nowtime;
}
};
業務上包括任務Task、和定時任務Timertask兩類,執行實體都是Task
Timertask重載<是因爲定時任務需要按時間臨的遠近排序,線程池的定時任務隊列的實現是一個堆,所以這裏需要重載<;flag_意爲是一次性定時任務還是例行定時任務。
這些非本部分關注點不影響閱讀即可。
2、線程池的聲明,重點關注多線程互斥鎖、條件變量成員
class ThreadPool {
private:
std::atomic<uint64_t> pending_num_;
std::atomic<uint64_t> running_num_;
uint64_t task_count_;
bool stop_;
int thread_num_;
std::vector<std::thread> ths_;
std::mutex mtx_;
std::condition_variable cond_;
std::deque<Task> queue_;
std::priority_queue<Timertask> timer_queue_;
public:
ThreadPool(int thread_num);
~ThreadPool();
bool Start();
bool Stop(bool graceful);
void AddTask(const Task &task);
void AddPriorityTask(const Task &task);
void AddDelayTask(int timeval, const Task &task);
void AddTimerTask(int timeval, const Task &task);
bool IsEmpty() {return (running_num_ > 0)?false:true;}
bool CancelTask();
static void *ThreadFunc(void *arg) {
reinterpret_cast<ThreadPool *>(arg)->process();
return 0;
}
void process();
};
重點關注析構,析構函數在"優雅模式"下,可以通過原子成員變量pending_num_獲知是否全部任務執行完畢
非優雅模式下,首先置stop_標誌位爲false意爲即將析構,並通過條件變量cond_的notify_all喚醒全部線程,使其執行完當前任務後退出
bool ThreadPool::Start () {
std::unique_lock<std::mutex> lock(mtx_);
stop_ = false;
for (auto i: common::Range(0, thread_num_)) {
ths_.push_back(std::thread(&ThreadFunc, this));
}
}
bool ThreadPool::Stop (bool graceful) {
if (graceful) {
while (pending_num_) {
std::chrono::milliseconds duration(5000);
std::this_thread::sleep_for(duration);
}
}
stop_ = true;
cond_.notify_all();
for (auto i: common::Range(0, thread_num_)) {
ths_[i].join();
}
pending_num_ = running_num_ = task_count_ = 0;
}
線程池的線程的實際執行函數,在執行完當前任務後會發現stop_標誌位已經爲false了,會紛紛退出
每個線程被操作系統調度到後,首先霸佔互斥鎖,注意c++11的互斥鎖使用方法;
然後從任務隊列中取出任務,然後釋放掉互斥鎖,自己去執行任務;如果沒有任務,釋放鎖並一直等待條件變量的被通知
void ThreadPool::process () {
while (1) {
std::unique_lock<std::mutex> lock(mtx_);
while (timer_queue_.empty() && queue_.empty() && !stop_) {
cond_.wait(lock);
}
if (stop_) {
break;
}
if (!timer_queue_.empty()) {
int64_t nowtime = common::getime_micros();
Timertask newestask = timer_queue_.top();
if (newestask.exec_time_ <= nowtime) {
timer_queue_.pop();
Task task = newestask.task_;
bool flag = newestask.flag_;
int64_t timeval = newestask.timeval_;
if (flag) {
Timertask newtask(task, timeval, true);
timer_queue_.push(newtask);
++task_count_;
}
++running_num_;
--pending_num_;
lock.unlock();
task();
lock.lock();
--running_num_;
}
}
if (!queue_.empty()) {
Task task = queue_.front();
queue_.pop_front();
--pending_num_;
++running_num_;
lock.unlock();
task();
lock.lock();
--running_num_;
}
}
當給線程池加入新的要執行的任務,也會先霸佔鎖並向任務隊列里加入新的任務,然後通知某一個正在等待條件變量同步的sleeping的線程(notify_one):
普通任務以雙向數組std::deque管理,按是否重要選擇前插還是後插
void ThreadPool::AddTask (const Task &task) {
std::unique_lock<std::mutex> lock(mtx_);
queue_.push_back(task);
++pending_num_;
++task_count_;
cond_.notify_one();
}
void ThreadPool::AddPriorityTask (const Task &task) {
std::unique_lock<std::mutex> lock(mtx_);
queue_.push_front(task);
++pending_num_;
++task_count_;
cond_.notify_one();
}
void ThreadPool::AddDelayTask (int timeval, const Task &task) {
std::unique_lock<std::mutex> lock(mtx_);
Timertask newtask(task, timeval);
timer_queue_.push(newtask);
++task_count_;
cond_.notify_one();
}
void ThreadPool::AddTimerTask (int timeval, const Task &task) {
std::unique_lock<std::mutex> lock(mtx_);
Timertask newtask(task, timeval, true);
timer_queue_.push(newtask);
++task_count_;
cond_.notify_one();
}