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
using CompareOperator = std::function
using query_record = std::tuple
template
1.4、auto & decltype:
auto让编译器通过初始值来推算变量的类型。当然,其定义的变量必须要有初始值
auto a = 1;
auto task = std::function
................
});
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
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 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 c(1.1, 1);
}
2、容器
2.1、tuple & 花括号初始化
元组的出现,和python拉齐了,c++也实现了函数可以多个返回值
using result = std::tuple
result res = {1,'a',1.0};
return res;
return {2, 'b',100.0};
std::vector
using res_tp = std::tuple
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
如自定义类型class A,A的构造函数接收一个int型参数,
那么对于push_back需要是:
std::vector vec;
A a(10);
vec.push_back(a);
对于emplace_back则是:
std::vector vec;
vec.emplace_back(10);
改进点是什么?改进点:避免无用临时变量。比如上面例子中的那个a变量
2.4、shrink_to_fit
这个改进还是有点意义的,日常程序应该能减少不少无意义的内存空间占用
push、insert这类操作会触发容器的capacity,即预留内存的扩大,实际开发时往往这些扩大的区域并没有用途
std::vector 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;
};
一个构造函数,使用自己的参数,传递给其他构造函数去构造,作为自己的构造函数实现,
如下例,后面两个构造函数,均传递参数,委托给第一个构造函数去实现
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)
对临时变量(如函数中的参数)的复制,通过更改对象的所有者(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 t_;
test(std::vector &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 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可以打印出其持有的全局字符串列表
如果一个老容器如vector容器oldv,需要将其内部数据复制给新容器如vector容器newv,且老容器后面无用,数据量很大;
那么c++11的std::make_move_iterator将派上用场,它可以将一个普通迭代器,如oldv.begin(),转化为"move式迭代器",配合std::copy,将老容器内全部数据的引用,move给新容器同时取消老容器对数据的持有权。这就是c++11风格的高速数据拷贝方式。
std::vector oldv = {"123", "456", "789"};
std::vector 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;
作用于虚函数,更多的作用是:显式的标识是否应该多态继承或不应该
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 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 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 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 modify_add3 = std::function([=, &b]{
b = a + a + a;
return a + b;
});
LOG(INFO) << "directly in-function: " << modify_add3();
int func4 (const int &a, int &b) {
b = 3;
return a + b;
}
int func5 (int *a) {
return *a;
}
std::function 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 modify_add5 = std::bind(func5, std::placeholders::_1);
LOG(INFO) << "args is const reference and reference: " << modify_add5(&a);
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(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 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 agent_;
agent_.reset(new std::thread([this] () {
while (1) {
std::unique_lock 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在这方面同样大量移植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;
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 pending_num_;
std::atomic running_num_;
uint64_t task_count_;
bool stop_;
int thread_num_;
std::vector ths_;
std::mutex mtx_;
std::condition_variable cond_;
std::deque queue_;
std::priority_queue 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(arg)->process();
return 0;
}
void process();
};
重点关注析构,析构函数在"优雅模式"下,可以通过原子成员变量pending_num_获知是否全部任务执行完毕
非优雅模式下,首先置stop_标志位为false意为即将析构,并通过条件变量cond_的notify_all唤醒全部线程,使其执行完当前任务后退出
bool ThreadPool::Start () {
std::unique_lock 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 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_;
}
}
普通任务以双向数组std::deque管理,按是否重要选择前插还是后插
void ThreadPool::AddTask (const Task &task) {
std::unique_lock lock(mtx_);
queue_.push_back(task);
++pending_num_;
++task_count_;
cond_.notify_one();
}
void ThreadPool::AddPriorityTask (const Task &task) {
std::unique_lock lock(mtx_);
queue_.push_front(task);
++pending_num_;
++task_count_;
cond_.notify_one();
}
void ThreadPool::AddDelayTask (int timeval, const Task &task) {
std::unique_lock 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 lock(mtx_);
Timertask newtask(task, timeval, true);
timer_queue_.push(newtask);
++task_count_;
cond_.notify_one();
}