function与lambda示例
function的作用就是把函数、函数对象、仿函数等统一封装成对象,取代函数指针,常用于回调,类似函数的多态(一个统一接口),对于解耦代码很友好。
而bind和lambda就是创建函数对象传给他的,如std::thread()就要传一个函数对象。(建议使用lambda,即可绑定匿名回调)
#include <functional>
#include <unordered_map>
#include <iostream>
class CommandHandler {
private:
// 定义回调函数类型
using Callback = std::function<void()>;
// 存储命令和对应的回调函数
std::unordered_map<int, Callback> callbacks;
public:
// 不要这样写,违反了开闭原则,类内的代码不能随意改
// void registerCallback() {
// callbacks[1] = []() { std::cout << "执行命令1" << std::endl; };
// callbacks[2] = []() { std::cout << "执行命令2" << std::endl; };
// callbacks[3] = []() { std::cout << "执行命令3" << std::endl; };
// }
// 注册回调函数
void registerCallback(int cmd, Callback callback) {
callbacks[cmd] = std::move(callback);
}
// 处理命令:查表执行回调
void handleCommand(int cmd) {
auto it = callbacks.find(cmd);
if (it != callbacks.end()) {
it->second();
} else {
std::cout << "未知命令: " << cmd << std::endl;
}
}
};
// 使用示例
int main() {
CommandHandler handler;
// 注册各种命令的回调函数,用lambda传的
handler.registerCallback(1, []() {
std::cout << "执行回调1" << std::endl;
});
handler.registerCallback(2, []() {
std::cout << "执行回调2" << std::endl;
});
handler.registerCallback(3, []() {
std::cout << "执行回调3" << std::endl;
});
// 处理用户输入
while (1) {
std::cout << "请输入命令(1-3,0退出):";
int cmd = 0;
std::cin >> cmd;
if (cmd == 0) break;
handler.handleCommand(cmd);
}
}
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,还可以手动t.request_stop(); 请求停止线程
互斥锁
锁的本质就是只能指针,构造函数里加锁,析构函数里解锁,所以出作用域就解锁了
注意: c++11的mutex、cv、sem都不能用于进程间同步,若要进程间同步:可以用共享内存+pthread_mutex/sem_t等,参考操作系统-进程篇
#include <mutex>
std::mutex mtx;
{
//省略了模板定义,因为c++17后都可以自动推导了,如std::vector v = {1};
//但一般不省,锁这里省掉不影响可读性。
std::unique_lock lock(mtx);//lock是锁名,出作用域自动解锁,也可手动加解锁或设置加锁超时时间
std::lock_guard lock(mtx); //更轻量级,只能自动解锁,功能少。
xxx
}
读写锁
读共享,写独占,适用于读多写少的场景;posix的:pthread_rwlock_t
#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++线程池
posix的:pthread_cond_t cv;
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
{
std::unique_lock lck(mtx);
//释放锁,等待notify
cv.wait(lck);
/* wait函数执行步骤:被唤醒->检查条件: 满足就获得锁返回,不满足继续沉睡
这种写法很好,可以避免虚假唤醒 */
cv.wait(lock, [&](){ return stop || !tasks.empty(); });
//释放锁,等待一定时间,超时自动醒
cv_.wait_for(lock, std::chrono::milliseconds(connectTimeout_));
cv.notify_all();//唤醒所有线程
cv.notify_one();//唤醒一个线程获得锁
}
信号量
c++20, 一般也是配合锁使用;posix的:sem_t sem;
信号量代表了资源的数量,如同时访问数据库的最大线程数。
#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
//sem.release(5); //sem+5
}
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++; //不用加锁