目录
c++面试必考多线程,内存(智能指针),常见算法,设计模式。
一 多线程
c++11提供了mutex和condition_variable,并没有提供临界区,信号量。(线程同步)
Mutex互斥量,C++ 11中使用 std::mutex类,必须包含 <mutex> 头文件。
(1)Mutex系列类(四种)
std::mutex,最基本的 Mutex 类。构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。lock()还是try_lock()都需要和unlock()配套使用。但性能损耗,推荐使用atomic操作数据类型
std::recursive_mutex,递归 Mutex 类。
std::time_mutex,定时 Mutex 类。
std::recursive_timed_mutex,定时递归 Mutex 类。
(2)Lock系列类(两种)----------这两个类相比使用std::mutex的优势在于不用配对使用,无需担心忘记调用unlock而导致的程序死锁
std::lock_guard,方便线程对互斥量上锁。除了构造函数外没有其他成员函数
std::unique_lock,除了lock_guard的功能外,提供了更多的成员函数,相对来说更灵活一些。这些成员函数包括lock,try_lock,try_lock_for,try_lock_until、unlock等
(3) 条件变量
std::condition_variable 提供了两种 wait() 函数
无条件等待:
void wait (unique_lock<mutex>& lck);
有条件等待
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
std::condition_variable::notify_one() 唤醒其中一个等待线程(不确定)
std::condition_variable::notify_all() 唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做
(4)线程池实现原理
//ThreadPool.h
class ThreadPool{
public:
//自定义void()的函数类型
typedef function<void()>Task;
ThreadPool();
~ThreadPool();
public:
size_t initnum;
//线程数组
vector<thread>threads ;
//任务队列
queue<Task>task ;
//互斥锁条件变量
mutex _mutex ;
condition_variable cond ;
//线程池工作结束时为真
bool done ;
//队列是否为空
bool isEmpty ;
//队列是否为满
bool isFull;
public:
void addTask(const Task&f);
void start(int num);
void setSize(int num);
void runTask();
void finish();
};
//ThreadPool.cpp
#include"ThreadPool.h"
ThreadPool ::ThreadPool():done(false),isEmpty(true),isFull(false){
}
//设置池中初始线程数
void ThreadPool::setSize(int num){
(*this).initnum = num ;
}
//添加任务
void ThreadPool::addTask(const Task&f)
{
if(!done){
//保护共享资源
unique_lock<mutex>lk(_mutex);
//要是任务数量到了最大,就等待处理完再添加
while(isFull){
cond.wait(lk);
}
//给队列中添加任务
task.push(f);
if(task.size()==initnum)
isFull = true;
cout<<"Add a task"<<endl;
isEmpty = false ;
cond.notify_one();
}
}
void ThreadPool::finish()
{
//线程池结束工作
for(size_t i =0 ;i<threads.size();i++){
threads[i].join() ;
}
}
void ThreadPool::runTask(){
//不断遍历队列,判断要是有任务的话,就执行
while(!done){
unique_lock<mutex>lk(_mutex);
//队列为空的话,就等待任务
while(isEmpty){
cond.wait(lk);
}
Task ta ;
//转移控制快,将左值引用转换为右值引用
ta = move(task.front());
task.pop();
if(task.empty()){
isEmpty = true ;
}
isFull =false ;
ta();
cond.notify_one();
}
}
void ThreadPool::start(int num){
setSize(num);
for(int i=0;i<num;i++){
threads.push_back(thread(&ThreadPool::runTask,this));
}
}
ThreadPool::~ThreadPool(){
}
//Test.cpp
#include <iostream>
#include"ThreadPool.h"
void func(int i){
cout<<"task finish"<<"------>"<<i<<endl;
}
int main()
{
ThreadPool p ;
p.start(N);
int i=0;
while(1){
i++;
//调整线程之间cpu调度,可以去掉
this_thread::sleep_for(chrono::seconds(1));
auto task = bind(func,i);
p.addTask(task);
}
p.finish();
return 0;
}
二 指针
0 智能指针
shared_ptr,本质是类模板,包含2个要素:1要管理的资源,2引用计数(atomic原子变量实现,线程安全)。
每次创建类的新对象时,初始化指针并将引用计数count=1,拷贝构造函数count++,赋值运算符重载左操作数所指对象的引用计数count--,右操作数所指对象的引用计算count++
实现原理:引入2个类,一个引用计数类Counter,一个指针类SmartPtr
注意事项:1 不能将原始指针直接赋值给shared_ptr,不要将同一个原始指针初始化不同的shared_ptr,不要循环引用,不要delete get函数获取的原始指针,删除器的使用
weak_ptr,配合shared_ptr使用,观测权,无引用计数
| 与shared_ptr不同,unique_ptr没有定义类似make_shared的操作,因此只可以使用new来分配内存,并且由于unique_ptr不可拷贝和赋值,初始化unique_ptr必须使用直接初始化的方式。
unique_ptr 同一时刻只能有一个unique_ptr指向给定对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值。 unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权 |
shread_ptr和unique_ptr都可以持有数组,但有区别,如下:
shared_ptr默认是使用delete来释放管理的资源,delete只会调用第一个元素的析构函数。要使用shared_ptr来管理数组,就需要需要自定义删除器
unique_ptr重载了管理数组的版本, unique_ptr就会自动使用delete[]来释放资源
1 new int[12]生成一个大小为12 的int数组,而new int(12)是生成一个初始值为12的int变量。
对于基本类型数组来说,delete a和delete [ ] a效果是一样的,如果a是一个用户自己定义的结构类型数组,只能使用delete [ ] a。
2下面的代码会输出:
int main()
{
int a[4]={1,2,3,4};
int *ptr=(int *)(&a+1);
printf("%d",*(ptr-1));
}
解析:最终结果会输出4。考察数组和指针, 指针加一的能力由指针指向的类型决定。&a和a都指的是数组首元素的地址,不同的是,a就是a+0,*(a+0)也就是a[0],而&a+1相当于对a[]类型的数组类型的指针加一,此时指针加到数组的末尾。ptr接受之后,ptr指向最后一个元素后面的那个位置,而ptr的类型是int*,因此

本文概述了C++中多线程的mutex、condition_variable及其实现,智能指针如shared_ptr和unique_ptr的用法,以及内存管理、算法和设计模式等内容,适合面试准备。
最低0.47元/天 解锁文章
3万+





