腾讯 QQ-客户端 一面

1、单例模式的注意事项,有哪些需要注意的

答案:单例模式本身不难,难的是细节。真正要注意的点主要是线程安全、生命周期、析构时机、拷贝控制和全局依赖污染。如果是多线程环境,初始化过程必须保证线程安全;如果单例里持有文件句柄、网络连接、共享内存句柄这类资源,还要考虑进程退出时能不能安全释放。另外单例对象一般都不应该允许拷贝和赋值,否则“全局唯一”这个语义就被破坏了。还有一个很容易被忽略的问题是,单例虽然方便,但会让模块之间形成隐式依赖,测试时也不太好替换,所以不能什么都往单例上套。

如果用 C++11 之后的写法,通常直接用局部静态对象实现就够了,初始化是线程安全的。

代码:

class Singleton {
public:
    static Singleton& instance() {
        static Singleton obj;
        return obj;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    ~Singleton() = default;
};

2、简单说一下线程池的原理

答案:线程池的核心思路就是预先创建一批工作线程,任务来了以后不再频繁创建和销毁线程,而是把任务投递到一个共享任务队列里,由空闲线程取任务执行。这样做主要是为了降低线程创建销毁成本,控制并发数量,避免系统在高并发下被大量线程拖垮。

一个完整线程池通常会有这几个部分:任务队列、工作线程集合、同步机制、退出控制。主线程负责提交任务,工作线程阻塞等待队列里的任务,拿到任务就执行;如果线程池关闭,就通知所有工作线程退出。如果面试官继续问深一点,还可以讲到任务窃取、动态扩缩容、拒绝策略、无界队列和有界队列的区别。

代码:

#include <thread>
#include <queue>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <functional>

class SimpleThreadPool {
public:
    explicit SimpleThreadPool(size_t n) : stop_(false) {
        for (size_t i = 0; i < n; ++i) {
            workers_.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(mtx_);
                        cv_.wait(lock, [this] {
                            return stop_ || !tasks_.empty();
                        });
                        if (stop_ && tasks_.empty()) return;
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }

    void submit(std::function<void()> task) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            tasks_.push(std::move(task));
        }
        cv_.notify_one();
    }

    ~SimpleThreadPool() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stop_ = true;
        }
        cv_.notify_all();
        for (auto& t : workers_) {
            if (t.joinable()) t.join();
        }
    }

private:
    std::vector<std::thread> workers_;
    std::queue<std::function<void()>> tasks_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stop_;
};

3、进程间大数据通信方案,你怎么选择

答案:如果是大数据量、同机房同机器上的多个进程通信,我优先考虑共享内存。原因是共享内存避免了多次用户态和内核态之间的数据拷贝,吞吐量和延迟都比较有优势。如果数据量不大,但更关注编程简单性和边界清晰,可以选 pipe、socketpair、Unix Domain Socket。如果是跨机器,那就不是进程间通信了,一般要走 TCP、RDMA 或者应用层协议栈。

选择时我一般看这几个维度:数据量大小、是否需要跨主机、通信双方关系是否固定、是否需要可靠顺序语义、实现复杂度是否可控。像配置同步、状态通知这种小消息,没必要上共享内存;但如果是本机多进程之间传大块数据,比如图像帧、批量样本、列式数据块,那共享内存会更合适。

4、共享内存的使用流程是什么样子的

答案:共享内存的基本流程就是:创建或打开共享内存对象,设置大小,把它映射到进程地址空间,然后双方通过这块映射区域读写数据,使用完成后解除映射并回收。如果是 System V 共享内存,通常是 shmgetshmatshmdtshmctl;如果是 POSIX 共享内存,通常是 shm_openftruncatemmapmunmapshm_unlink

流程本身不复杂,真正难的是同步和布局。共享内存里不能只想着“能写进去”,还要考虑头部怎么设计、偏移量怎么维护、生产者消费者怎么同步、异常退出后怎么恢复现场。

代码:

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <iostream>

int main() {
    const char* name = "/demo_shm";
    int fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    ftruncate(fd, 4096);

    void* ptr = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    std::strcpy((char*)ptr, "hello shared memory");
    std::cout << (char*)ptr << std::endl;

    munmap(ptr, 4096);
    close(fd);
    shm_unlink(name);
    return 0;
}

5、共享内存有哪些接口

答案:共享内存主要分两类接口。

一类是 System Vshmget 用来创建或获取共享内存段,shmat 把共享内存挂接到进程地址空间,shmdt 解除挂接,shmctl 做控制操作,比如删除、查看状态。

另一类是 POSIXshm_open 创建或打开共享内存对象,ftruncate 设置大小,mmap 映射到进程空间,munmap 解除映射,shm_unlink 删除对象。

现在工程里很多人更习惯 POSIX 这套,因为接口风格和文件描述符体系更统一,配合 mmap 也更自然。

6、共享内存线程安全吗?怎么保护

答案:共享内存本身只是“共享一块地址空间映射”,它不提供线程安全,也不提供进程安全。多个线程或者多个进程同时读写共享内存,如果没有额外同步,照样会出现竞态、脏读、覆盖和数据结构损坏。

保护方式通常要看场景。如果是多线程共享,最直接就是互斥锁、读写锁、原子变量、条件变量。如果是多进程共享,可以用进程共享互斥锁、信号量、futex,或者基于原子变量实现无锁结构。另外还要注意一个问题:同步原语本身如果也要跨进程共享,必须放在共享内存里,并且初始化时设置成 PTHREAD_PROCESS_SHARED

代码:

#include <pthread.h>

pthread_mutex_t mtx;
pthread_mutexattr_t attr;

void init_process_shared_mutex() {
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&mtx, &attr);
}

7、线程下面开辟多个协程,等于说一个线程可以有多个协程?

答案:可以。协程本质上是用户态调度单元,不是操作系统内核线程。一个线程里完全可以跑很多个协程,这些协程共享同一个线程的地址空间和内核执行流,只是在用户态主动切换执行上下文。所以“一个线程多个协程”是协程模型里非常常见的

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论
大佬是客户端还是后端呀?
点赞 回复 分享
发布于 03-16 13:59 浙江

相关推荐

03-24 18:18
南京大学 Java
因为项目写得很简陋,所以以为会多问八股,结果还是拷打项目为主两个项目,一个电商平台,一个上学期用unity写的avg游戏。电商平台部分,因为自己简历写的负责登录鉴权,所以这部分问得比较多1.&nbsp;你原来的架构具体是什么样的?分层和模块化是怎么设计的?2.&nbsp;之前鉴权机制存在什么问题?新的&nbsp;JWT&nbsp;+&nbsp;Spring&nbsp;Security&nbsp;有什么优势?3.&nbsp;Spring&nbsp;Security&nbsp;+&nbsp;JWT&nbsp;的用户认证流程是怎样的?鉴权部分的具体细节?4.&nbsp;JWT&nbsp;的&nbsp;token&nbsp;刷新机制是什么样的?5.&nbsp;只有一个&nbsp;token&nbsp;吗?6.&nbsp;没有设计两个&nbsp;token&nbsp;吗(access&nbsp;token&nbsp;和&nbsp;refresh&nbsp;token)?一个短一点一个长一点?7.&nbsp;支付的状态你是怎么设计的?8.&nbsp;假如支付成功了,但因为网络原因给你返回了失败,后期你们怎么处理?9.&nbsp;如果返回的错误码不是支付失败,而是网络请求失败或超时,但用户实际已支付成功,这种情况怎么处理?10.&nbsp;数据分类、榜单这类场景,你是怎么设计缓存策略的?11.&nbsp;如果用&nbsp;Redis,中间加了一层缓存区,怎么保证缓存与数据库的一致性?12.&nbsp;优惠券系统有没有像淘宝那样复杂的满减、红包等规则?13.&nbsp;设计购买流程时有没有考虑过高并发情况?14.&nbsp;有没有做过性能监控或优化?比如数据库查询时间等。15.&nbsp;你了解过有哪些工具可以查询数据库的慢查询情况吗?16.&nbsp;项目里会加很多日志吗?17.&nbsp;除了&nbsp;token&nbsp;认证,有没有考虑过其他安全方面的保护(如&nbsp;XSS、SQL&nbsp;注入)?18.&nbsp;你对&nbsp;XSS&nbsp;和&nbsp;SQL&nbsp;注入有了解吗?avg游戏项目1.&nbsp;对话窗口的对话树是怎么存储的?2.&nbsp;物品栏的拖拽等交互是怎么触发的?3.&nbsp;游戏进度(如剧情进度、好感度)是怎么做持久化的?4.&nbsp;持久化具体用的什么格式(JSON&nbsp;还是自定义二进制)?5.&nbsp;图片、音频等资源是怎么管理的?6.&nbsp;Unity&nbsp;中的&nbsp;async/await&nbsp;和协程有什么区别?八股部分1.&nbsp;网络通讯方面有用到&nbsp;TCP&nbsp;和&nbsp;UDP&nbsp;吗?它们的区别是什么?你主要用哪个?2.&nbsp;TCP&nbsp;的拥塞控制有哪些方法?3.&nbsp;HTTP&nbsp;的端点续传原理是什么?4.&nbsp;你主要用&nbsp;Java&nbsp;还是&nbsp;C++?5.&nbsp;C++&nbsp;的内存管理是什么样的?6.&nbsp;RAII&nbsp;最有代表的&nbsp;STL&nbsp;容器是哪些?如果不用&nbsp;new/delete,你会用什么写法避免内存泄漏?7.&nbsp;智能指针用过吗?讲一讲它们的特点。8.&nbsp;基类的析构函数为什么需要是虚函数?9.&nbsp;`vector`&nbsp;的&nbsp;`emplace_back`&nbsp;和&nbsp;`push_back`&nbsp;有什么区别?手撕部分1.&nbsp;一个加强版的二分查找,要求找到&nbsp;target&nbsp;的最小&nbsp;index&nbsp;和最大&nbsp;index。项目还是没准备好,面试官人很不错,可能因为学历是9加上客户端的原因,问得比较简单,但还是没答好就是了。
点赞 评论 收藏
分享
评论
点赞
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务