std :: string в многопоточной программе - PullRequest
34 голосов
/ 02 ноября 2009

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

1) Стандарт C ++ 03 никак не касается существования потоков

2) Стандарт C ++ 03 оставляет реализациям решать, следует ли std::string использовать семантику Copy-on-Write в своем конструкторе копирования

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

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

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

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

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

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

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

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

Ответы [ 8 ]

11 голосов
/ 19 ноября 2009

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

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

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

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

8 голосов
/ 02 ноября 2009

Вы правы. Это будет исправлено в C ++ 0x. На данный момент вы должны полагаться на документацию вашей реализации. Например, последние версии libstdc ++ (GCC) позволяют использовать строковые объекты , как если бы ни один строковый объект не разделяет свой буфер с другим. C ++ 0x заставляет реализацию библиотеки защищать пользователя от «скрытого обмена».

4 голосов
/ 23 декабря 2009

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

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

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

4 голосов
/ 02 ноября 2009

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

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

2 голосов
/ 21 декабря 2009

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

В этой статье представлено сравнение строк STL с копированием при записи и без копирования. алгоритмы записи, основанные на строках, веревках STLport и GNU libstdc ++ Реализации.

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

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

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

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

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

0 голосов
/ 21 декабря 2009

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

0 голосов
/ 02 ноября 2009

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

// instead of:
string newString = oldString;

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

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

string newString(oldString.begin(), oldString.end());
0 голосов
/ 02 ноября 2009

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

  • сделать std::string участников приватными
  • возврат const std::string& для добытчиков
  • сеттеры изменяют член

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...