function与bind示例
function的作用就是把函数、函数对象、仿函数等统一封装成对象,取代函数指针,常用于回调,类似函数的多态(一个统一接口),对于解耦代码很友好。
而bind和lambda就是创建函数对象传给他的,如std::thread()就要传一个函数对象。(建议使用lambda,即可绑定匿名回调,游客调用函数间接实现绑定有名回调)
#include <iostream>
using namespace std::placeholders; //bind的命名空间
#include <functional>
class test {
public:
unordered_map<int, function<void(string b)>> map;
void f1(int a, string b) {cout << a << b << endl;}
void f2(int a, string b) {cout << a << b << endl;}
test() {//bind的作用就是赋值给function<void(string b)>
map[1] = std::bind(&test::f1, this, std::placeholders::_1, std::placeholders::_2);//占位
map[2] = bind(&test::f2, this, std::placeholders::_1, "固定");//绑定固定值,后续就不用传该位置形参
map[2] = [&](int a, string b){this->f2(a, b)};//使用lambda表达式绑定回调,&会捕捉到this指针,或者直接[this]也可以。
}
};
int main(int argc, char* argv[]) {
test t;
// 得到对应回调,直接执行,因为function重载了函数调用运算符(),这里其实就是多态的体现。
t.map[atoi(argv[1])](argv[2]);
}
c++11线程库
c++11线程库:语言级别好处是跨平台,且不用-pthread
线程使用
#include <thread>
std::thread t1(myfunc,2);这就是一个线程,且可传参
//或者用匿名函数
std::thread t([](int a){
std::cout<< a << std::endl;
}, 2);
t1.detach();
//若是成员函数回调成员函数,就用lambda表达式:
class Test {
public:
void func(int a) {
std::thread t([&](){raft(a);}); //&捕获,捕获到了this指针,自然能使用类成员
t.detach();
}
private:
void raft(int a) {}
};
c++20的jthread :自动join或detach
互斥锁
锁的本质就是只能指针,构造函数里加锁,析构函数里解锁,所以出作用域就解锁了
注意: c++11的mutex、cv、sem都不能用于进程间同步,若要进程间同步:pthread_mutex,参考操作系统-进程篇
#include <mutex>
std::mutex mtx;
{
//省略了模板定义,因为c++17后都可以自动推导了,如std::vector v = {1};
//但一般不省,锁这里省掉不影响可读性。
std::unique_lock lock(mtx);//lock是锁名,出作用域自动解锁,也可手动加解锁或设置加锁超时时间
std::lock_guard lock(mtx); //更轻量级,只能自动解锁,功能少。
xxx
}
读写锁
读共享,写独占,适用于读多写少的场景
#include <shared_metex>
#include <mutex>
std::shared_metex sMtx;
{
//获取读锁,自动解锁;若是想用数据,可以先把数据存下来,然后赶紧解锁。
std::shared_lock lock(sMtx);
xxx;
}
{
//获取写锁,自动解锁
std::unique_lock lock(sMtx); // 加写锁
xxx
}
//也可手动加解锁,适合封装使用
std::shared_mutex rw_mtx;
rw_mtx.lock(); //加写锁
rw_mtx.unlock(); //释放写锁
rw_mtx.lock_shared(); //加共享读锁
rw_mutex.unlock_shared(); //释放读锁
条件变量
配合锁使用,可参考C++线程池
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
{
std::unique_lock lck(mtx);
//释放锁,等待notify
cv.wait(lck);
//释放锁,等待一定时间,超时自动醒
cv_.wait_for(lock, std::chrono::milliseconds(connectTimeout_));
//notify强制唤醒,满足条件也自动醒,
cv.wait(lock, [&](){ return stop || !tasks.empty(); });
cv.notify_all();//强制唤醒所有线程,逐个获得锁
cv.notify_one();//强制唤醒一个线程获得锁
}
信号量
c++20, 一般也是配合锁使用
信号量代表了资源的数量,如同时访问数据库的最大线程数。
#include <iostream>
#include <unistd.h>
#include <thread>
#include <semaphore>
//本例信号量代表:最多10个线程同时访问worker函数
std::counting_semaphore<10> sem(2); //<10>: 表示信号量的上限,(2): 初始化信号量数
void worker(int id) {
sem.acquire(); //若sem>0,就获取,sem-1; 若sem=0, 就阻塞,直到sem>0
std::cout << "Worker " << id << " is working." << std::endl;
sleep(1); // 模拟工作
std::cout << "Worker " << id << " is done." << std::endl;
sem.release(); // 释放, sem+1
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
std::thread t3(worker, 3);
t1.join();
t2.join();
t3.join();
}
结果如图:
t1, t2是并发执行的,因为刚开始有2个信号量,而t3需要等他们release才获得sem。
原子类型
可以保证对单个变量操作的线程安全
#include <atomic>
//<T>只能是整数或指针
std::atomic<int> a;
std::atomic<Node*> p;
a++; //不用加锁