Подтвердить что ты не робот

Std::string в многопоточной программе

Учитывая, что:

1) Стандарт С++ 03 не затрагивает существование потоков каким-либо образом.

2) Стандарт С++ 03 оставляет за собой возможность реализовать, чтобы std::string использовать семантику Copy-on-Write в своем конструкторе-копире

3) Семантика Copy-on-Write часто приводит к непредсказуемому поведению в многопоточной программе

Я прихожу к следующему, казалось бы, спорный, вывод:

Вы просто не можете безопасно и портативно использовать std::string в многопоточной программе

Очевидно, что структура данных STL не является потокобезопасной. Но, по крайней мере, с помощью std::vector, например, вы можете просто использовать мьютексы для защиты доступа к вектору. При реализации std::string, использующей COW, вы не можете надежно сделать это без редактирования семантики подсчета ссылок в рамках реализации поставщика.

Пример реального мира:

В моей компании у нас есть многопоточное приложение, которое было тщательно протестировано и прошло через Valgrind бесчисленное количество раз. Приложение работало месяцами без каких-либо проблем. Однажды я перекомпиляю приложение на другую версию gcc, и внезапно я получаю случайные segfaults все время. Valgrind теперь сообщает о недопустимых доступах к памяти в пределах libstdС++ в конструкторе std::string.

Итак, каково решение? Ну, конечно, я мог бы typedef std::vector<char> как строковый класс, но на самом деле это отстой. Я также могу дождаться С++ 0x, и я молюсь, чтобы разработчики отказались от COW. Или, (дрожь), я мог бы использовать собственный класс строк. Я лично всегда выступаю против разработчиков, которые реализуют свои собственные классы, когда существующая библиотека будет работать нормально, но, честно говоря, мне нужен класс строк, который, я уверен, не использует семантику COW; и std::string просто не гарантирует этого.

Я прав, что std::string просто нельзя использовать надежно вообще в переносных многопоточных программах? И что хорошего обходного пути?

4b9b3361

Ответ 1

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

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

Ответ 2

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

В стандарте также нет ничего гарантировать, что vector можно использовать так, как вы говорите. Было бы законно предоставить реализацию С++ с расширением потока, в котором, скажем, любое использование вектора вне потока, в котором оно было инициализировано, приводит к поведению undefined. В тот момент, когда вы запускаете второй поток, вы больше не используете стандартный С++, и вы должны посмотреть на своего поставщика компилятора на то, что безопасно, а что нет.

Если ваш поставщик предоставляет расширение для потоковой передачи, а также предоставляет std::string COW, который (следовательно) не может быть потокобезопасным, тогда я думаю, что пока ваш аргумент связан с вашим поставщиком или с расширением потоковой передачи, а не со стандартом С++. Например, возможно, POSIX должен был запретить строки COW в программах, которые используют pthreads.

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

Ответ 3

Вы правы. Это будет исправлено в С++ 0x. Пока вы должны полагаться на свою документацию по внедрению. Например, последние версии libstdС++ Versions (GCC) позволяют использовать строковые объекты, как если бы ни один строковый объект не делится своим буфером с другим. С++ 0x заставляет библиотеку внедряться для защиты пользователя от "скрытого совместного использования".

Ответ 4

Более правильным способом взглянуть на это будет "Вы не можете безопасно и портативно использовать С++ в многопоточной среде". Нет никакой гарантии, что другие структуры данных будут вести себя разумно. Или что время выполнения не взорвет ваш компьютер. Стандарт ничего не гарантирует о потоках.

Итак, чтобы сделать что-либо с потоками на С++, вам нужно полагаться на определенные им гарантии. И тогда вы можете безопасно использовать std::string, потому что каждая реализация говорит вам, безопасно ли это использовать в потоковой среде.

Вы потеряли всякую надежду на истинную мобильность в тот момент, когда вы создали вторую нить. std::string не является "менее переносимым", чем остальная часть языка/библиотеки.

Ответ 5

Вы можете использовать STLport. Он содержит строки, отличные от COW. И он имеет такое же поведение на разных платформах.

В этой статье представлено сравнение строк STL с копированием и записью, on-write, основанные на строках STLport, канатах и ​​GNU libstdС++ реализации.

В компании, где я работаю, у меня есть опыт работы с тем же серверным приложением, созданным с помощью STLport, и без STLport на HP-UX 11.31. Приложение было скомпилировано с gcc 4.3.1 с уровнем оптимизации O2. Поэтому, когда я запускаю progrma, построенную с помощью STLport, он обрабатывает запросы на 25% быстрее по сравнению с той же самой программой, созданной без STLport (которая использует gcc собственную библиотеку STL).

Я профилировал обе версии и выяснил, что версия без STLport тратит гораздо больше времени на pthread_mutex_unlock() (2,5%) по сравнению с версией с STLport (1%). И pthread_mutex_unlock() сам в версии без STLport вызывается из одной из функций std::string.

Однако, когда после профилирования я изменил назначения строк на наиболее часто называемые функции следующим образом:

string_var = string_var.c_str(); // added .c_str()

произошло значительное улучшение производительности версии без STLport.

Ответ 6

Я регулирую доступ к строкам:

  • сделать std::string членов private
  • return const std::string& для геттеров
  • сеттеры модифицируют элемент

Это всегда срабатывало для меня и корректно скрывало данные.

Ответ 7

Если вы хотите отключить семантику COW, вы можете заставить ваши строки делать копии:

// instead of:
string newString = oldString;

// do this:
string newString = oldString.c_str();

Как указано, особенно если у вас есть встроенные нули, вам следует использовать итератор ctor:

string newString(oldString.begin(), oldString.end());

Ответ 8

В MSVC std::string больше не ссылается на общий общий указатель на контейнер. Они выбирают всю ценность содержимого в каждом конструкторе копирования и операторе присваивания, чтобы избежать многопоточных проблем.