何时使用c++类锁定互斥对象

When to lock mutex with c++ class

本文关键字:对象 锁定 c++ 何时使      更新时间:2023-10-16

我目前正在处理一个接受多个客户端的服务器。

在服务器端,我有一个线程池(手工制作,运行良好),可以容纳多个线程:

ThreadPool::bind(new TCPReceiver());
ThreadPool::bind(new TCPSender());

一旦一个类绑定到ThreadPool,就会调用它的start()函数。

所以我的服务器基本上做的是:

  • 绑定线程
  • 接受一个或多个客户端
  • 将客户端的指针添加到TCPReceiver客户端列表
    • TCPReceiver执行socket.receive(),将接收到的数据推送给消息队列中的Clients
  • 将客户端的指针添加到TCPSender客户端列表
    • TCPSender执行socket.send()并发送客户端的输出消息队列

因此,一旦连接了客户端,其类的指针就会连接到两个线程,一个读取套接字,一个在套接字上发送。在所有这些过程中,主线程(服务器)弹出客户端的输入消息队列。

class Server {
     std::list<Client*> clients;
     TCPReceiver receive;
     TCPSender   send;
     public:
     void *start();
}
class Client {
   std::list<NetworkMessage*> inQueue;
   IMutex *inMutex;
   std::list<NetworkMessage*> outQueue;
   IMutex *outMutex;
   Socket  *socket;
}
class TCPReceiver {
    std::list<Client*> clients;
     public:
     void *start();
}
class TCPSender {
    std::list<Client*> clients;
     public:
     void *start();
}

我的问题是:

从Server/TCPReceiver/TCPSender类,我可以在不锁定Client类的情况下访问/使用Client指针,但只锁定Client的消息队列以弹出/推送它吗?

两个线程是否可以同时调用不同客户端的成员函数?

我可以在不锁定std::list的情况下调用std::list的成员函数吗(请参见(*it)->inQueue.empty()调用)?

void Server::start() {
   for (std::list<Client*>::iterator it = this->clients.begin(); it != this->clients.end(); ++it) {
    if (!(*it)->inQueue.empty()) {
        (*it)->inMutex->lock();
        (*it)->inQueue.front();
        (*it)->inQueue.pop_front();
        (*it)->inMutex->unlock();
    }
   }
} 

同时在TCPReceiver上:

void TCPReceiver::start() {
    for (std::list<Client*>::iterator it = this->clients.begin(); it != this->clients.end(); ++it) {
        std::string msg = (*it)->socket->receive();
        if (!msg.empty()){ 
           (*it)->inMutex->lock();
           (*it)->inQueue.push_back(msg);
           (*it)->inMutex->unlock();
        }
    }
}

(我知道socket也应该有一个互斥,但这不是我现在想要理解的)

是的,两个线程确实可以同时执行同一实例的方法。您需要某种同步机制来防止由于同时修改相同的值而导致的竞争条件。在这方面,读取的危险性不亚于写入,因为在写入操作进行时进行读取可能会导致读取垃圾值。

基本上,这意味着在检查队列是否为空之前,您应该锁定(因为您的线程可能在该行和下一行之间挂起),但您不需要在迭代客户端的循环之外锁定,前提是确保客户端列表在迭代过程中不会更改。

您需要确保在对象处于无效状态时,没有线程访问任何对象。对于像您所描述的生产者-消费者类型的情况,您可能有兴趣了解条件变量,它提供了一种等待某种状态改变的方法。(例如,等待一个不再为空的空队列)。

请注意,在您提供的示例中,您只在clients上循环一次,并在每个队列中最多添加/删除一条消息。