简体   繁体   中英

When to lock mutex with c++ class

I'm currently working on a server accepting multiple clients.

On server side I've a threadpool (hand made, works fine) that lunch multiple threads :

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

Once a class is binded to the ThreadPool , its start() function is called.

So basicaly what my server do is :

  • bind threads
  • accept one or more Client
  • add client's pointer to TCPReceiver clients list
    • TCPReceiver execute socket.receive() , and push the received data to Clients in message queue
  • add client's pointer to TCPSender clients list
    • TCPSender execute socket.send() and send Client's output message queue

So once a client is connected, its class's pointer is attached to 2 threads, one who read the socket, one to send on socket. While all this, the main thread (Server) pop the Client's input message queue.

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();
}

My question(s) is :

From Server/TCPReceiver/TCPSender classes, can I access/use the Client pointers without locking the Client class, but only lock Client's message queue to pop/push on it ?

Does 2 threads can call different Client's members functions at the same time ?

Can I call std::list's members functions without locking the std::list (see (*it)->inQueue.empty() call) ?

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();
    }
   }
} 

meanwhile on 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();
        }
    }
}

(I know socket should have a mutex too, but it's not what I'm trying to understund right now)

Yes, two threads can indeed execute methods of the same instance concurrently. You need some kind of synchronization mechanism to prevent race conditions caused by modifying the same values concurrently. Reading is no less dangerous than writing in that regard, as reading while a write operation is in progress may result in garbage values being read.

Basically, that means that you should lock before checking whether the queue is empty, (as your thread might be suspended between that line and the next one), but you don't need to lock outside of the loop iterating over the clients, provided the list of clients is guaranteed not to change during the iteration.

You need to make sure that no thread accesses any of your objects while it is in an invalid state. For a producer-consumer kind of situation like the one you are describing, you may be interested in learning about condition variables , which provide a means of waiting for some state to change. (eg waiting for an empty queue not being empty any more).

Note that in the example you provided, you only loop over clients once and add/remove at most one message from/to each queue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM