多线程客户端可以使用一个socket吗?怎么保证线程得到想要的数据?

开发 前端
多线程共享一个socket的场景也还是常见的。只不过多个线程同时对这个socket进行读写操作,可能会出现数据错乱。也就是题目问的怎么保证线程得到想要的数据?

答案是可以,其实在项目中,多线程共享一个socket的场景也还是常见的。只不过多个线程同时对这个socket进行读写操作,可能会出现数据错乱。也就是题目问的怎么保证线程得到想要的数据?

我想到的有两种处理方法 

第一种:单线程读/写+消息队列分发 

思路就是:让一个专门的线程负责socket的读写,其他线程通过队列和这个线程通信。

设计步骤: 

1、线程A专门负责读取 socket 数据,并将数据分发到其他线程。 

2、使用消息队列(如 std::queue )保存 socket 接收到的数据,并根据数据类型或标志位,将数据分发给需要的线程。 

3、写操作也通过消息队列统一到专门的线程A执行,避免冲突。

这种设计的优点:保证线程间的数据一致性,避免多线程直接读写 socket 的冲突。很多需要保证顺序的业务也是采用这种方式。 

这种把某个业务功能放在一个线程,其他线程配合的做法,很多项目用到,例如redis,主逻辑也是单线程处理,然后也用到了很多队列和这个主逻辑线程进行配合。

一个简单的示例: 

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

std::queue<std::string> message_queue; // 消息队列
std::mutex queue_mutex;
std::condition_variable cond_var;

void socket_reader(int socket_fd) {
    while (true) {
        char buffer[1024];
        int bytes = recv(socket_fd, buffer, sizeof(buffer), 0);
        if (bytes > 0) {
            std::string message(buffer, bytes);

            // 加入队列
            {
                std::lock_guard<std::mutex> lock(queue_mutex);
                message_queue.push(message);
            }
            cond_var.notify_one();
        }
    }
}

void worker_thread() {
    while (true) {
        std::unique_lock<std::mutex> lock(queue_mutex);
        cond_var.wait(lock, [] { return !message_queue.empty(); });

        // 从队列中取出消息
        std::string message = message_queue.front();
        message_queue.pop();
        lock.unlock();

        // 处理消息
        std::cout << "Worker thread received: " << message << std::endl;
    }
}

int main() {
    int socket_fd = 0; // 假设 socket_fd 已经初始化

    // 启动读线程和工作线程
    std::thread reader_thread(socket_reader, socket_fd);
    std::thread worker(worker_thread);

    reader_thread.join();
    worker.join();
    return 0;
}


第二种:如果多线程必须都要直接操作socket,那么第一种方式就不满足了,这个时候就要用到锁机制了。 

思路就是:多个线程要直接操作同一个 socket,使用互斥锁来保证同一时刻只有一个线程访问 socket。 

设计步骤: 

1、在读/写操作时加锁,保证数据的完整性。 

2、每个线程可以根据协议中的标志或数据格式,解析出属于自己的数据。 

这种做法效率会稍微低点,因为锁的粒度相当于放大了。更重要的是耦合性也增加了,而第一种方案耦合性很低,读写线程就只管读写,其他线程负责处理数据。

简单示例: 

#include <iostream>
#include <thread>
#include <mutex>

std::mutex socket_mutex;

void read_from_socket(int socket_fd, int thread_id) {
    std::lock_guard<std::mutex> lock(socket_mutex);

    char buffer[1024];
    int bytes = recv(socket_fd, buffer, sizeof(buffer), 0);
    if (bytes > 0) {
        std::string data(buffer, bytes);
        std::cout << "Thread " << thread_id << " received: " << data << std::endl;
    }
}

void write_to_socket(int socket_fd, const std::string& message) {
    std::lock_guard<std::mutex> lock(socket_mutex);
    send(socket_fd, message.c_str(), message.size(), 0);
}

int main() {
    int socket_fd = 0; // 假设 socket_fd 已经初始化

    std::thread t1(read_from_socket, socket_fd, 1);
    std::thread t2(read_from_socket, socket_fd, 2);

    t1.join();
    t2.join();
    return 0;}


暂时只想到这两种方案,如果你有更好的方案,欢迎在评论区提出。

总结:

如果需要高性能和数据一致性,使用单线程读/写 + 消息队列分发。

如果场景简单,可以通过互斥锁直接控制多线程访问。

责任编辑:武晓燕 来源: CppPlayer
相关推荐

2010-03-17 18:04:55

java Socket

2009-06-08 20:16:15

Eclipse客户端多线程

2011-12-15 11:03:21

JavaNIO

2022-03-14 09:35:43

Pythonhttpx

2019-02-25 14:54:37

开源播客客户端gPodder

2019-07-22 20:03:23

Docker操作系统Linux

2010-03-18 16:49:43

Java Socket

2020-02-25 09:45:05

Dino开源XMPP

2017-07-24 10:18:55

LinuxNylas Mail邮件客户端

2016-10-09 08:35:09

Linux桌面REST

2021-11-29 07:47:57

gRPCGUI客户端

2021-09-11 15:26:23

Java多线程线程池

2024-02-01 14:59:14

多线程硬件系统

2011-08-17 10:10:59

2012-12-07 10:15:53

IBMdW

2020-10-26 13:12:00

多线程调度随机性

2022-06-13 07:33:57

socketReact组件

2023-09-04 08:08:59

2011-03-21 14:53:36

Nagios监控Linux

2011-04-06 14:24:20

Nagios监控Linux
点赞
收藏

51CTO技术栈公众号