文章目录
- 绑定器和函数对象
- 函数对象
- 绑定器
- lambda表达式
- 关键词与语法
- auto
- nullptr
- 右值引用
- 智能指针
- 容器
- set和map
- unordered_set和unordered_map
- 数组
- 链表
- 语言级别支持的多线程编程
- thread
- 子线程如何结束
- 主线程如何处理子线程
- 线程间的互斥
- 线程的同步通信机制(条件变量)
- 总结
绑定器和函数对象
函数对象
使用函数对象时,不需要事先定义一个类。
例子:优先级队列:
priority_queue<int, vector<int>, greater<int>> ay;
当你使用 greater<int>
时,你实际上是在创建一个 greater<int>
的实例,而不是调用一个函数。在这种情况下,不需要使用括号。括号通常用于调用函数,而不是创建函数对象的实例。
排序算法:
std::sort(vec.begin(), vec.end(), std::greater<int>());
greater 是一个函数对象类,它定义了一个函数调用运算符 operator(),用于比较两个元素的大小。
然而,如果你要使用函数对象来进行比较操作,你需要使用括号来调用该函数对象的函数调用运算符 operator()。这个函数调用运算符是函数对象类中的一个成员函数,用于执行比较操作。例如:
greater<int> comp;
bool result = comp(5, 10); // 调用函数对象的函数调用运算符
在上面的示例中,comp 是greater<int>
的一个实例,通过调用函数对象的函数调用运算符,可以将 5 和 10 进行比较。
需要注意的是, std::greater<int>()
是一个二元函数对象,可以被用于比较两个值的大小。
如果 std::greater() 是一个一元函数对象,它将无法正确地用作 std::sort 的比较器。因为 std::sort 需要一个接受两个参数的比较函数对象来进行排序操作。
但是在优先队列的模板参数中,我们只需提供函数对象类型,而不需要调用它的函数调用运算符。优先队列会在内部使用这个函数对象进行比较操作。因此,在优先队列的模板参数中,我们只需提供函数对象的类型 greater<int>
,而不需要调用它。
绑定器
函数绑定器(Function binders)是一种函数适配器
bind1st和bind2nd在STL中主要用于二元函数对象,将其中的一元绑定成一个固定的量,成为一元函数变量。
bind1st和bind2nd+二元函数对象 =》 一元函数对象
以greater<int>
为例,如果我现在想在一个已经排序完成的数组vec中在正确的位置插入70,可以使用bind1st绑定
将70按顺序插入vec容器中--》找一个小于70的数字
operator()(const T &val)
greater a > b
less a < b
auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70);
或
auto it1 = find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70);
if(it1 != vec.end())
{
vec.insert(it1, 70);
}
bind是bind1st和bind2nd的升级,他们本身也是函数对象。
#include <iostream>
#include <functional>
void printValues(int a, int b) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
int main() {
// 使用 std::bind1st
auto printA = std::bind1st(std::ptr_fun(printValues), 42);
printA(10); // 输出:a: 42, b: 10
// 使用 std::bind2nd
auto printB = std::bind2nd(std::ptr_fun(printValues), 20);
printB(30); // 输出:a: 30, b: 20
// 使用 std::bind
auto printC = std::bind(printValues, std::placeholders::_1, std::placeholders::_2);
printC(50, 60); // 输出:a: 50, b: 60
return 0;
}
lambda表达式
关键词与语法
详情见:博客
auto
根据右值,推导出右值的类型
nullptr
指针专用。(之前的NULL指针和整数是混用的)
右值引用
move移动语义函数和forward类型完美转发函数
智能指针
详情见:博客
shared_ptr和weak_ptr
容器
详情见:博客
set和map
增删插的效率很高
红黑树为底层,有序容器
unordered_set和unordered_map
哈希表为底层,增删插的效率为O(1)
数组
array
vector 建议使用
链表
farword_list双向链表
list 建议使用
语言级别支持的多线程编程
概念见:博客
之前都是使用的操作系统的接口,如:
createThread pthread_create clone
这样对跨平台很不友好,兼容性差。
thread
int main
{
// 创建了一个线程对象,传入一个线程函数,新线程就开始运行了
std::thread t1(threadHandle1, 2);
xxxxx 主程序
// 主线程等待子线程结束,主线程继续往下运行
//t1.join();
//把子线程设置为分离线程
t1.detach();
cout << "main thread done!" << endl;
当主线程运行完成时,如果查看当前进程还有未运行完成的子线程,进程就会异常终止
return 0;
}
子线程如何结束
子线程函数运行完成,线程结束
主线程如何处理子线程
1、主线程等待子线程结束 join
2、把子线程设置为分离线程,不管子线程 detach
线程间的互斥
竞争态条件,有不是线程安全的代码=》临界区代码段=》保障原子操作=》互斥锁mutex
所以临界区代码需要=》操作=》互斥锁
锁+双重判断
线程的同步通信机制(条件变量)
场景:线程1必须依赖线程2的通知
例子:生产者消费者模型
1、创建线程安全的lockQueue
std::mutex mtx; // 定义互斥锁,做线程间的互斥操作
std::condition_variable cv; // 定义条件变量,做线程间的同步通信操作
// 生产者生产一个物品,通知消费者消费一个;消费完了,消费者再通知生产者继续生产物品
class Queue // 对queue重新封装一下
{
public:
void put(int val) // 生产物品
{
//lock_guard<std::mutex> guard(mtx); // scoped_ptr 不能同时使用两把锁
unique_lock<std::mutex> lck(mtx); // unique_ptr
while (!que.empty())
{
// que不为空,生产者应该通知消费者去消费,消费完了,再继续生产
// 生产者线程进入#1等待状态(阻塞状态),并且#2把mtx互斥锁释放掉 消费者线程就能抢到这把锁 不释放锁 无法消费
cv.wait(lck); // lck.lock() lck.unlock
}
que.push(val);
/*
notify_one:通知另外的一个线程的
notify_all:通知其它所有线程的
通知其它所有的线程,我生产了一个物品,你们赶紧消费吧
其它线程得到该通知,就会从等待状态 =》 阻塞状态 =》 获取互斥锁才能继续执行
*/
cv.notify_all();
cout << "生产者 生产:" << val << "号物品" << endl;
}
int get() // 消费物品
{
//lock_guard<std::mutex> guard(mtx); // scoped_ptr
unique_lock<std::mutex> lck(mtx); // unique_ptr
while (que.empty())
{
// 消费者线程发现que是空的,通知生产者线程先生产物品
// #1 进入等待状态 # 把互斥锁mutex释放
cv.wait(lck);
}
int val = que.front();
que.pop();
cv.notify_all(); // 通知其它线程我消费完了,赶紧生产吧
cout << "消费者 消费:" << val << "号物品" << endl;
return val;
}
private:
queue<int> que;
};
void producer(Queue *que) // 生产者线程 生产10个物品
{
for (int i = 1; i <= 10; ++i)
{
que->put(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer(Queue *que) // 消费者线程
{
for (int i = 1; i <= 10; ++i)
{
que->get();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
Queue que; // 两个线程共享的队列
std::thread t1(producer, &que);
std::thread t2(consumer, &que);
t1.join(); // 主线程等到两个子线程执行完 继续执行
t2.join();
return 0;
}
总结
线程间互斥 : 临界区 原子类型 互斥锁 信号量
线程间同步 : 条件变量 信号量
1、std::mutex
std::mutex mtx;
mtx.lock()
mtx.unlock()
2、lock_guard
只能在简单的临界区代码段的互斥操作中,不可能用在函数参数传递或者返回函数中。
因为函数参数传递或者返回过程中都会用到拷贝构造和赋值,但是lock_guard拷贝构造和赋值(引用和右值引用)都被delete了。
lock_guard<std::mutex> guard(mtx)
3、unique_lock
不仅能在简单的临界区代码段的互斥操作中,还能用在函数参数传递(函数调用)中。
虽然unique_lock底层左值引用的拷贝构造和赋值被delete,但是它提供了右值引用的拷贝构造和赋值。
unique_lock<std::mutex> lck(mtx)
4、条件变量
std::condition_variable cv;
cv.wait(lck); //guard输入不了,因为lock_guard没有拷贝构造
// 使线程进入等待状态,并且把lck.unlock可以把mtx释放掉
cv.notify_all();
// 通知再cv上等待的线程,条件成立了,起来干活了
// 收到通知=》从等待状态到阻塞状态=》获取互斥锁了=》线程继续执行
notify_one();
// 通知再cv上等待的一个线程