Потокобезопасная реализация объекта, который удаляет себя - PullRequest
1 голос
/ 29 июля 2010

У меня есть объект, который вызывается из двух разных потоков, и после того, как он был вызван обоими, он уничтожает себя путем «удаления этого».

Как реализовать этот потокобезопасный?Потокобезопасный означает, что объект никогда не уничтожает себя ровно один раз (он должен уничтожить себя после второго обратного вызова).

Я создал пример кода:

class IThreadCallBack                                                                                             
{                                                                                                                 
  virtual void CallBack(int) = 0;                                                                                 
};                                                                                                                

class M: public IThreadCallBack                                                                                   
{                                                                                                                 
private:                                                                                                          
  bool t1_finished, t2_finished;                                                                                  

public:                                                                                                           

  M(): t1_finished(false), t2_finished(false)                                                                     
  {                                                                                                               
    startMyThread(this, 1);                                                                                       
    startMyThread(this, 2);                                                                                       
  }                                                                                                               

  void CallBack(int id)                                                                                           
  {                                                                                                               
    if (id == 1)                                                                                                  
    {                                                                                                             
      t1_finished = true;                                                                                         
    }                                                                                                             
    else                                                                                                          
    {                                                                                                             
      t2_finished = true;                                                                                         
    }                                                                                                             

    if (t1_finished && t2_finished)                                                                               
    {                                                                                                             
      delete this;                                                                                                
    }                                                                                                             
  }                                                                                                               
};                                                                                                                

int main(int argc, char **argv) {                                                                                 
  M* MObj = new M();                                                                                              
  while(true);                                                                                                    
}                                                                                                                 

Очевидно, я не могуиспользуйте Mutex в качестве члена объекта и заблокируйте удаление, потому что это также удалит Mutex.С другой стороны, если я установлю флаг «toBeDeleted» внутри защищенной мьютексом области, где установлен флаг finised, я не уверен, что возможны ситуации, когда объект вообще не удаляется.Обратите внимание, что реализация потока гарантирует, что метод обратного вызова вызывается ровно один раз на поток в любом случае.

Редактирование / обновление: Что, если я изменю Callback (..) на:

void CallBack(int id)

{    
  mMutex.Obtain()
  if (id == 1)
  {
    t1_finished = true;
  }
  else
  {
    t2_finished = true;
  }
  bool both_finished = (t1_finished && t2_finished);

  mMutex.Release();

  if (both_finished)
  {
    delete this;     
  }
}

Может ли это считаться безопасным?(с mMutex, являющимся членом класса m?)

Я думаю, что если я не получу доступ ни к одному члену после освобождения мьютекса?!

Ответы [ 5 ]

3 голосов
/ 29 июля 2010

Использование Интеллектуальный указатель повышения . Это обрабатывает это автоматически; Ваш объект не должен будет удалять себя, и он безопасен для потоков.

Редактировать
Из кода, который вы опубликовали выше, я не могу сказать, нужно больше информации. Но вы можете сделать это следующим образом: каждый поток имеет объект shared_ptr, и когда вызывается обратный вызов, вы вызываете shared_ptr :: reset (). При последнем сбросе будет удален M. Каждый shared_ptr может быть сохранен с локальным хранилищем потока в каждом потоке. По сути, каждый поток отвечает за свой собственный shared_ptr.

3 голосов
/ 29 июля 2010

Вместо использования двух отдельных флагов, вы можете установить счетчик на число ожидающих потоков, а затем использовать взаимосвязанный декремент.

Тогда вы можете быть на 100% уверены, что когда счетчик потока достигнет 0, все готово, и вы должны очиститься.

Для получения дополнительной информации о взаимосвязанном декременте в Windows , в Linux и в Mac .

2 голосов
/ 29 июля 2010

Я не могу себе представить, что это возможно, особенно внутри самого класса. Проблема в два раза:

1) Нет способа уведомить внешний мир о том, что он не должен вызывать объект, поэтому внешний мир должен отвечать за установку указателя на 0 после вызова «CallBack», если указатель был удален.

2) Как только два потока входят в эту функцию, вы, и простите мой французский, абсолютно трахались. Вызов функции для удаленного объекта - это UB, просто представьте, к чему приведет удаление объекта, пока кто-то находится в нем.

Я никогда не видел "удалить это" как что-либо, кроме мерзости. Это не значит, что иногда, в ОЧЕНЬ редких условиях, это необходимо. Проблема в том, что люди слишком много делают и не думают о последствиях такого дизайна.

Я не думаю, что "быть удаленным" будет работать хорошо. Это может работать для двух потоков, но как насчет трех? Вы не можете защитить часть кода, которая вызывает удаление, потому что вы удаляете защиту (как вы заявляете) и из-за UB, которую вы неизбежно вызовете. Итак, первый проходит, устанавливает флаг и отменяет .... кто из остальных собирается вызвать delete при выходе?

2 голосов
/ 29 июля 2010

Однажды я реализовал что-то вроде этого, чтобы полностью избежать неприятностей и путаницы с delete this, действуя следующим образом:

  • Запустить поток, отвечающий за удаление этих общих объектов, который ожидает условие
  • Когда общий объект больше не используется, вместо того, чтобы удалить себя, он вставляет себя в потокобезопасную очередь и сигнализирует о том, что поток средства удаления ожидает
  • Когда поток удаляет, он удаляет все в очереди

Если в вашей программе есть цикл обработки событий, вы можете избежать создания отдельного потока для этого, создав тип события, который означает «удалить неиспользуемые общие объекты», и заставить некоторый постоянный объект реагировать на это событие так же, как Удаление потока будет в приведенном выше примере.

1 голос
/ 29 июля 2010

Более надежной реализацией было бы внедрение подсчета ссылок.Для каждого потока, который вы запускаете, увеличьте счетчик;для каждого обратного вызова уменьшайте счетчик и, если счетчик достиг нуля, удаляйте объект.Вы можете lock получить доступ к счетчику, или вы можете использовать класс Interlocked для защиты доступа к счетчику, хотя в этом случае вам нужно быть осторожным с возможной гонкой между окончанием первого потока и вторымзапуск.

Обновление : И, конечно, я полностью проигнорировал тот факт, что это C ++.:-) Вы должны использовать InterlockExchange для обновления счетчика вместо класса C # Interlocked.

...