MST

星途 面试题库

面试题:C++网络编程中的多线程与并发处理

在C++网络编程场景下,假设服务器需要同时处理多个客户端的请求,如何利用多线程技术来实现并发处理?请详细说明线程安全问题及相应的解决方案,同时给出代码示例展示线程池的设计与使用。
39.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

利用多线程技术实现并发处理

在C++网络编程中,为了同时处理多个客户端请求,可以为每个客户端请求创建一个新线程来处理,或者使用线程池。

  1. 为每个请求创建新线程: 当有新的客户端连接时,创建一个新的线程来处理该客户端的通信。例如:
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <functional>
#include <vector>
#include <chrono>

// 模拟处理客户端请求的函数
void handleClient(int clientId) {
    std::cout << "Handling client " << clientId << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Finished handling client " << clientId << std::endl;
}

在服务器主循环中:

int main() {
    for (int i = 0; i < 5; ++i) {
        std::thread t(handleClient, i);
        t.detach();
    }
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 0;
}
  1. 使用线程池: 线程池预先创建一定数量的线程,任务到来时将任务分配给线程池中的线程。

线程安全问题及解决方案

  1. 数据竞争
    • 问题:多个线程同时访问和修改共享数据,导致数据不一致。
    • 解决方案:使用互斥锁(std::mutex)、读写锁(std::shared_mutex)等。例如,保护共享资源sharedData
std::mutex dataMutex;
int sharedData = 0;

void modifySharedData() {
    std::lock_guard<std::mutex> lock(dataMutex);
    sharedData++;
}
  1. 死锁

    • 问题:两个或多个线程相互等待对方释放资源,导致程序冻结。
    • 解决方案:避免嵌套锁,按顺序加锁,使用std::try_lock尝试加锁等。
  2. 条件变量

    • 问题:线程需要等待某个条件满足才能继续执行。
    • 解决方案:使用std::condition_variable。例如,线程等待共享数据达到某个值:
std::mutex cvMutex;
std::condition_variable cv;
bool dataReady = false;

void waitForData() {
    std::unique_lock<std::mutex> lock(cvMutex);
    cv.wait(lock, [] { return dataReady; });
    std::cout << "Data is ready" << std::endl;
}

void setDataReady() {
    {
        std::lock_guard<std::mutex> lock(cvMutex);
        dataReady = true;
    }
    cv.notify_one();
}

线程池的设计与使用代码示例

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

class ThreadPool {
public:
    ThreadPool(size_t numThreads) {
        for (size_t i = 0; i < numThreads; ++i) {
            threads.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queueMutex);
                        this->condition.wait(lock, [this] { return this->stop ||!this->tasks.empty(); });
                        if (this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& thread : threads) {
            thread.join();
        }
    }

    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;
        auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task]() { (*task)(); });
        }
        condition.notify_one();
        return res;
    }

private:
    std::vector<std::thread> threads;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop = false;
};

// 示例任务
int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int main() {
    ThreadPool pool(4);
    std::vector<std::future<int>> results;

    for (int i = 1; i <= 8; ++i) {
        results.emplace_back(pool.enqueue(factorial, i));
    }

    for (auto& res : results) {
        std::cout << res.get() << std::endl;
    }

    return 0;
}

此代码实现了一个简单的线程池,包含线程创建、任务队列管理、线程安全机制以及条件变量的使用。可以将不同的任务通过enqueue方法提交到线程池中执行。