Написание многопоточного безопасного от исключения кода

class ListArray
  attr_accessor :arr
  def initialize(arr)
    @arr = arr
  end
  def arr_object_id
    @arr.object_id
  end
end

Создайте экземпляр ListArray, создав переменную экземпляра @arr, равную [0, 1, 2, 3]:

a = ListArray.new [0, 1, 2, 3] 
  #=> #<ListArray:0x0000574d960e19e8 @arr=[0, 1, 2, 3]> 

Давайте проверим значение @arr и получим его идентификатор объекта:

a.arr
  #=> [0, 1, 2, 3] 
a.arr_object_id
  #=> 47995370802440 

Теперь создайте еще один экземпляр ListArray, создав его переменную экземпляра @arr и установив его равным значению a.arr:

b = ListArray.new(a.arr)
  #=> #<ListArray:0x0000574d9611bdf0 @arr=[0, 1, 2, 3]> 
b.arr
  #=> [0, 1, 2, 3] 
b.arr_object_id
  #=> 47995370802440

Интересная вещь здесь это a.arr_object_id == b.arr_object_id. Это не удивительно, однако, поскольку мы инициализировали переменную экземпляра b переменной экземпляра a, поэтому это один и тот же объект !

Далее измените значение Переменная экземпляра a в [0, 1, 999, 3]:

a.arr[2] = 999
a.arr
  #=> [0, 1, 999, 3] 
a.arr_object_id
  #=> 47995370802420 

Проверьте, изменилось ли значение переменной экземпляра b:

b.arr
  #=> [0, 1, 999, 3] 
b.arr_object_id
  #=> 47995370802440

Имеет, потому что [ 1124] и переменные экземпляра b @arr содержат один и тот же объект.

Чтобы переменная экземпляра b содержала массив, переменные экземпляра которого совпадают с переменными a, но делают два массива различными объектами, создайте b с его переменной экземпляра @arr, равной Скопируйте значения переменной экземпляра a:

a = ListArray.new [0, 1, 2, 3]
  #=> #<ListArray:0x0000574d9610d818 @arr=[0, 1, 2, 3]> 
a.arr_object_id 
  #=> ...320 
b = ListArray.new(a.arr.dup)
  #=> #<ListArray:0x0000574d961143c0 @arr=[0, 1, 2, 3]> 
b.arr
  #=> [0, 1, 2, 3] 
b.arr_object_id
  #=> ...100 (different than a.arr_object_id)
a.arr[2] = 19
a.arr
  #=> [0, 1, 19, 3] 
b.arr
  #=> [0, 1, 2, 3] 

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

a = ListArray.new [0, [1, 2], 3]
  #=> #<ListArray:0x0000574d9614b370 @arr=[0, [1, 2], 3]> 
a.arr_object_id 
  #=> ...700 
a.arr[1].object_id
  #=> ...720 
a.arr[1][1].object_id
  #=> 5 
2.object_id
  #=> 5 

b = ListArray.new(a.arr.dup)
  #=> #<ListArray:0x0000574d96119258 @arr=[0, [1, 2], 3]> 
b.arr
  #=> [0, [1, 2], 3] 
b.arr_object_id
  #=> ...160  (different than a.arr_object_id)
b.arr[1].object_id
  #=> ...720 (same as a.arr[1].object_id) 
b.arr[1][1].object_id
  #=> 5 

Теперь измените значение a.arr[1][1]:

a.arr[1][1] = 9
a.arr
  #=> [0, [1, 9], 3] (as expected) 
a.arr[1].object_id
  #=> ...720 (no change) 
b.arr
  #=> [0, [1, 9], 3]
b.arr[1].object_id 
  #=> ...720 (no change)

Вы также видите это изменение b[1][1]. Это потому, что содержимое объекта , которое является значением как a.arr[1], так и b.arr[1], было изменено. Теперь попробуйте это.

a.arr[1] = [8, 0]
a.arr
  #=> [0, [8, 0], 3] (as expected) 
a.arr[1].object_id
  #=> ...880 (a new object!) 
b.arr
  #=> [0, [1, 9], 3] (unchanged!) 
b.arr[1].object_id 
  #=> ...720 (unchanged)

Для этого примера нам нужно написать:

a = ListArray.new [0, [1, 2], 3]
b = ListArray.new(a.arr.dup.map { |e| e.dup })

a.arr[1][1] = 9
a.arr
  #=> [0, [1, 9], 3] 
b.arr
  #=> [0, [1, 2], 3] (no change!)

a.arr.dup.map { |e| e.dup } упоминается как более глубокая копия из a.arr, чем a.arr.dup. Если бы еще были более глубокие вложенные массивы ([1, [2, [3, 4]], 5]), нам пришлось бы dup понизить уровни a.arr. Для новичка из Ruby не важно полностью понимать, как создаются глубокие копии , просто они необходимы для достижения независимости дублированных копий объектов.

10
задан Ian Nelson 1 December 2008 в 13:09
поделиться

9 ответов

Я полагаю, что стандарт C++ не делает упоминания о многопоточности - многопоточность является определенной для платформы функцией.

Я не абсолютно уверен, что стандарт C++ говорит о неперехваченных исключениях в целом, но согласно этой странице, что происходит, определяется платформой, и необходимо узнать в документации компилятора.

В быстром-и-грязном тесте я сделал с g ++ 4.0.1 (i686-apple-darwin8-g ++-4.0.1, чтобы быть конкретным), результат - это terminate() назван, который закрывает всю программу. Код, который я использовал, следует:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

Скомпилированный с g++ threadtest.cc -lpthread -o threadtest. Вывод был:

terminate called after throwing an instance of 'int'
4
ответ дан 3 December 2019 в 19:36
поделиться

C++ 0x будет иметь Поддержку Языка Переноса Исключений между Потоками так, чтобы, когда рабочий поток выдает исключение, родительский поток мог поймать или повторно бросить его.

Из предложения:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}
7
ответ дан 3 December 2019 в 19:36
поделиться

Это - единственная самая большая причина, что Erlang существует.

Я не знаю, какова конвенция, но по моему скромному мнению, быть максимально подобной Erlang. Сделайте "кучу", возражает неизменный и настраивает некоторый протокол передачи сообщений для передачи между потоками. Избегайте блокировок. Удостоверьтесь, что передача сообщений безопасна от исключения. Сохраните столько же материала с сохранением информации на стеке.

2
ответ дан 3 December 2019 в 19:36
поделиться

Неперехваченное исключение будет звонить terminate() который в свою очередь звонит terminate_handler (который может быть установлен программой). По умолчанию terminate_handler вызовы abort().

Даже если Вы переопределяете значение по умолчанию terminate_handler, в стандарте говорится, что стандартная программа, которую Вы обеспечиваете, "должна завершить осуществление программы, не возвращаясь к вызывающей стороне" (ISO 14882-2003 18.6.1.3).

Так, таким образом, неперехваченное исключение завершит программу не только поток.

Насколько потокобезопасность идет, как Adam Rosenfield говорит, это - платформа определенная вещь, это не обращено стандартом.

2
ответ дан 3 December 2019 в 19:36
поделиться

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

Но я хотел бы ответить на Ваш вопрос о силе между безопасностью исключения и потокобезопасностью.

В классе потокобезопасность уровня требует изменений в интерфейсе. Точно так же, как безопасность исключения делает. Например, это обычно для классов, чтобы возвратить ссылки на внутренние переменные, сказать:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

Если Foo совместно используется несколькими потоками, проблема ждет Вас. Естественно, Вы могли поместить взаимное исключение или другую блокировку для доступа к Foo. Но достаточно скоро, все программисты на C++ хотели бы перенести Foo в "ThreadSafeFoo". Моя конкуренция, то, что интерфейс для Foo должен быть изменен на:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

Да, это более дорого, но это может быть сделано ориентированным на многопотоковое исполнение с блокировками в Foo. IMnsHO это создает определенное количество силы между потокобезопасностью и безопасностью исключения. Или по крайней мере, необходимо выполнить больше анализа, поскольку каждый класс, используемый в качестве совместно используемого ресурса, должен быть исследован под обоими световыми сигналами.

2
ответ дан 3 December 2019 в 19:36
поделиться

Один классический пример (не может помнить, где я видел его сначала) находится в библиотеке станд.

Вот то, как Вы выталкиваете что-то от очереди:

T t;
t = q.front(); // may throw
q.pop();

Этот интерфейс является несколько тупым по сравнению с:

T t = q.pop();

Но сделан, потому что присвоение копии T может бросить. Если броски копии после поп происходят, тот элемент потерян от очереди и никогда не может восстанавливаться. Но так как копия происходит, прежде чем элемент выталкивается, можно поместить произвольную обработку вокруг копии с передней стороны () в блоках попытки/выгоды.

Недостаток - то, что Вы не можете реализовать очередь, которая ориентирована на многопотоковое исполнение со станд.:: интерфейс очереди из-за двух шагов включен. То, что хорошо для безопасности исключения (выделяющий шаги, которые могут бросить), теперь плохо для многопоточности.

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

2
ответ дан 3 December 2019 в 19:36
поделиться

Я не рекомендую позволить любому исключению остаться непойманным. Перенесите свои функции потока верхнего уровня во всеобъемлющие обработчики, которые могут более корректно (или по крайней мере verbosely) закрывает программу.

0
ответ дан 3 December 2019 в 19:36
поделиться

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

0
ответ дан 3 December 2019 в 19:36
поделиться

Существует две проблемы, которые я заметил:

  • в g ++ на Linux, уничтожение потока (pthread_cancel) выполняется путем выдавания "неизвестного" исключения. С одной стороны это позволяет Вам вымыться приятно, когда поток уничтожается. С другой стороны, если Вы ловите то исключение и не повторно бросаете его, Ваши концы кода с аварийным прекращением работы (). Поэтому, если Вы или какая-либо из библиотек, которыми Вы пользуетесь, уничтожаете потоки, Вы не можете иметь

    выгода (...)

без

throw;

в Вашем потоковом коде. Вот ссылка на это поведение в сети:

  • Иногда необходимо передать исключение между потоками. Это не легкая вещь сделать - мы закончили тем, что делали somehack, когда надлежащим решением является вид маршалинга / демаршалинг, Вы использовали бы между процессами.
1
ответ дан 3 December 2019 в 19:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: