可以先传入 function
int main(){
unordered_map< string, function > function_table
{
{ "+", [](int x, int y) {return x + y;} },
{ "-", [](int x, int y) {return x - y;} },
{ "*", [](int x, int y) {return x * y;} },
{ "/", [](int x, int y) {return x / y;} }
};
vector> vec{
{ [](int x, int y) {return x + y;} },
{ [](int x, int y) {return x - y;} },
{ [](int x, int y) {return x * y;} },
{ [](int x, int y) {return x / y;} }
};
//--------------------------------------------------------------------------
auto cmp = [](const pair& a, const pair& b)
{ return a.first < b.first; };
map map{ cmp };
cout << typeid(decltype(vec[0])).name() << endl;
cout << typeid(op_dict["+"]).name() << endl;
cout << typeid(cmp).name() << endl;
/*
class std::function
class std::function
class
*/
}
对于如上述代码所示,lambda作为容器的存储成员时,因为对于容器进行实例化的类型为function<>类型,因此传入的lambda表达式就会成为lambda的函数指针的形式在容器中进行存储。
在上述代码中,对于下面部分的map的初始化过程时,传入的lambda表达式是作为一个比较函数来进行map对于key的排序,传入的类型是class
使用lambda的好处:
1、lambda可以直接作为参数,作为参数类类型是 class lambda,好处是,可以直接捕获作用域内的变量,使用起来更加方便,省去了传参的过程
2、因此可以使用lambda表达式作为线程的参数,可以很好的捕捉当前环境下的变量
3、lambda本身就一直被视为一个可调用对象,可以直接在lambda表达式后跟函数参数
饱汉模式:在static类型的类对象p定义时就进行初始化,使用时调用instance静态函数来获取类对象p。不会有多线程的冲突,但是会在不需要p时持续占用资源空间。
饿汉模式:在类对象p定义时并不进行初始化,而是在使用时通过instance进行初始化,因此使用饿汉模式需要对于多线程的情况进行冲突处理。
class lazySingleTon {
private:
static lazySingleTon* p;
lazySingleTon() {}
static mutex lock_;
public:
static lazySingleTon* instance() {
std::lock_guard guard(lock_);
if (!p) {
p = new lazySingleTon();
}
return p;
}
};
lazySingleTon* lazySingleTon::p = nullptr;
}
进入instance函数就利用mutex互斥量来保证同一时间只有一个线程能够进入instance函数,这样的方式虽然达到了互斥的效果,但是对于第一次之后的instance函数调用,并不需要进行互斥,知识应该返回p供调用者使用,因此这样的方式浪费了大量的时间效率,
class lazySingleTon {//不上锁的话,同时调用instance会导致同时实例化两个实例并赋值给p
private:
static lazySingleTon* p;
lazySingleTon() {}//把构造函数放入private 的目的是为了达到外界不能随意创建实例,必须通过instance才能够创建实例
static mutex lock_;
public:
static lazySingleTon* instance() {
//双重检查锁,只有在p为nullptr时才会进行上锁,避免了大多数的无用上锁情况
if (!p) {
unique_lock _lock(lock_);// 双重检查锁,使得大多数情况不再进行加锁
_lock.lock();
if (!p) {
p = new lazySingleTon();
}
_lock.unlock();
}
return p;
}//instance 函数只有在第一次被创建时才有必要进行加锁,因为后续不再分配和创建存储空间,而是直接返回p
struct GarbageCollection {//内嵌垃圾回收类
~GarbageCollection() {
delete lazySingleTon::p;
}
};
static GarbageCollection gc;//类static成员,程序结束时进行自动析构,因此会调用GC类的析构函数,从而delete 实例p
};
lazySingleTon* lazySingleTon::p = nullptr;
DCL双重锁机制在进入instance函数时并不是立刻将mutex进行上锁,而是先判断 p 是否是nullptr,即p是否未初始化,如果此时p未初始化,说明p需要在本次调用中进行初始化,因此应该对于mutex进行上锁保证同一时刻只有一个线程在运行instance函数
GarbageCollection类是一个内嵌的垃圾回收类,通过GarbageCollection的析构函数来对于lazySingleTon中定义的堆变量 p 进行delete,并且定义一个类内的static CarbageCollection类型的成员,该static成员变量的生存周期在程序结束时到期,因此在程序结束时会自动调用GarbageCollection的析构函数对于 static p进行析构。
DCL双重锁机制还可能存在的一种问题:p = new lazySingleTon();的乱序带来的问题,这句命令有三个步骤:
1、分配存储lazySingleTon的存储空间
2、构造lazySingleTon实例化对象
3、将已经创建好的内存赋值给p
这是一个正确的顺序,但是在实际过程中,只能够保证1是按照顺序最先执行的,而2和3不能够保证哪个先进行
可能会产生,p已经赋值给了一段内存,但是该内存还未进行实例化,就有其他的线程调用instance导致返回了一个未构造完成的实例化对象
new内存分配乱序的解决方法:
1、遵循1、2、3的步骤依次编写代码:
singleton *tmp = static_cast(operator new(sizeof(singleton)));
new(p)singleton();
p = tmp;
2、使用硬件进行隔离屏蔽:
#define barrier() __asm__ volatile ("lwsync") 在 p = new lazySingleTon(); 前调用barrier()进行隔离3、C++11之后的一种简洁写法:返回局部的static成员
singleton *singleton::instance() { static singleton p; //不再在类内定义p指针,而是直接在instance内定义和初始化p,并返回p的地址 return &p; }
C++ 11规定了一个线程在初始化一个变量的时候,其他线程只有等待该初始化完成之后才能进行访问4、Automic C++ 11 之后提供了Automic ,可以定义一个原子类型 ,对于原子类型的操作都将是原子操作,自带加锁
std::atomic_thread_fence(std::memory_order_acquire); 获取是内存读操作,任何在此后面的内存操作不允许重排到前面
std::atomic_thread_fence(std::memory_order_release); 释放是内存写操作,任何在此前面的内存操作不允许重排到后面
memory_order_relaxed:松散内存序,只用来保证对原子对象的操作是原子的
在执行 p = new lazySingleTon(); 语句之前调用 acquire 操作,执行之后调用release操作,可以保证该内存调用操作是原子的,不会发生调用前使用和调用后使用的情况
5、 使用pthread_once:将 执行线程 &pthread_once_t 与 执行函数 初始化函数init 绑定到pthread_once函数,保证只被执行一次,如下:
class singleton { private: singleton(); //私有构造函数,不允许使用者自己生成对象 singleton(const singleton &other); //要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数) static void init() { p = new singleton(); } static pthread_once_t ponce_; static singleton *p; //静态成员变量 public: singleton *instance() { // init函数只会执行一次 pthread_once(&ponce_, &singleton::init); return p; } };
小的知识点:
1、mutex m.lock m.unlock 是最基本的互斥锁
lock_guard 是一个管理mutex的类,声明lock_guard对象,会在该对象的作用域内对该mutex进行上锁,作用域结束解锁
unique_lock 与lock_guard的区别在于 在生成unique_lock的时候不会对mutex立即上锁,可以灵活加锁和解锁
mutex是最原始的,而lock_guard和unique_lcok都是对于 对象变量的管理类
2、ostringstream 的使用:可以讲内容输出到ostringstream中,然后通过调用成员函数.str()将输出的内容全部取出保存为字符串,即将ostringstream视为一个缓冲容器
3、thread的两个成员函数 .join() 和 .detach() :join函数被调用时,主线程会阻塞于此,直到等待被调用join成员函数的线程执行完毕之后主线程才会继续执行。普通情况下,主线程会等待其子线程全部结束之后再结束,而对子线程调用detach之后主线程便可以提前结束,不需要等待该子线程。