Я хочу получить доступ к контейнеру на основе STL только для чтения из parallel работающих потоков. Без использования каких-либо пользовательских блокировок. Основой следующего кода является С++ 11 с правильной реализацией стандарта.
http://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html
http://www.sgi.com/tech/stl/thread_safety.html
http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/threadsintro.html
http://www.open-std.org/jtc1/sc22/wg21/ (текущий проект или N3337, который по существу является С++ 11 с незначительными ошибками и опечатками)
23.2.2 Гонки данных контейнера [container.requirements.dataraces]
В целях избежания расследований данных (17.6.5.9) реализации рассмотрим следующие функции: const, begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, в и, за исключением ассоциативных или неупорядоченных ассоциативных контейнеров, Оператор [].
Несмотря на (17.6.5.9), необходимы реализации чтобы избежать гонок данных, когда содержимое содержащегося объекта в различные элементы в одной и той же последовательности, за исключением вектора <bool> , измененный одновременно.
[Примечание: для вектора <int> x с размером больше чем один, x [1] = 5 и * x.begin() = 10 могут выполняться одновременно без гонки данных, но x [0] = 5 и * x.begin() = 10 выполнено одновременно может привести к гонке данных. В качестве исключения для общего правило, для вектора <bool> y, y [0] = true может участвовать в гонке с y [1] = true. - конечная нота]
и
17.6.5.9 Уклонение от гонки данных [res.on.data.races] 1 В этом разделе указаны требования, которые должны выполняться реализацией для предотвращения данных рас (1.10). Каждая стандартная библиотечная функция должна соответствовать каждому если не указано иное. Реализации могут данных в случаях, отличных от указанных ниже.
2 Стандарт С++ библиотечная функция не должна прямо или косвенно обращаться к объектам (1.10), доступный для потоков, отличных от текущей доступ к объектам осуществляется прямо или косвенно с помощью функций аргументы, включая это.
3 Стандартная библиотечная функция С++ должна не прямо или косвенно изменять объекты (1.10), доступные по потокам кроме текущего потока, если только объекты не доступны напрямую или косвенно через функции неконстантных аргументов, включая это.
4 [Примечание: это означает, например, что реализации не могут использовать статический объект для внутренних целей без синхронизации потому что это может привести к гонке данных даже в программах, которые не явно обмениваются объектами между потоками. - конечная нота]
5 Стандартная библиотечная функция С++ не должна косвенно обращаться к объектам доступный через его аргументы или через элементы его контейнера аргументы, за исключением вызова функций, требуемых по его спецификации на этих элементах контейнера.
6 Операции с итераторами, полученные вызов стандартного контейнера библиотеки или функции члена строки может доступ к базовому контейнеру, но не должен его изменять. [Примечание: в в частности, операции с контейнерами, которые лишают законных прав итераторов с операциями с итераторами, связанными с этим контейнером. - конец примечание]
7 Реализации могут делиться своими собственными внутренними объектами между потоки, если объекты не видны пользователям и защищены против гонок данных.
8 Если не указано иное, стандартная библиотека С++ функции должны выполнять все операции исключительно в рамках текущего если эти операции имеют видимые эффекты (1.10) - пользователей.
9 [Примечание: это позволяет реализациям распараллеливать операции если нет видимых побочных эффектов. - конечная нота]
Заключение
Контейнеры не являются потокобезопасными! Но безопасно вызывать функции const для контейнеров из нескольких параллельных потоков. Таким образом, можно выполнять операции только для чтения из параллельных потоков без блокировки.
Я прав?
Я притворяюсь, что их не существует какая-либо неисправная реализация, и каждая реализация стандарта С++ 11 верна.
Пример:
// concurrent thread access to a stl container
// g++ -std=gnu++11 -o p_read p_read.cpp -pthread -Wall -pedantic && ./p_read
#include <iostream>
#include <iomanip>
#include <string>
#include <unistd.h>
#include <thread>
#include <mutex>
#include <map>
#include <cstdlib>
#include <ctime>
using namespace std;
// new in C++11
using str_map = map<string, string>;
// thread is new in C++11
// to_string() is new in C++11
mutex m;
const unsigned int MAP_SIZE = 10000;
void fill_map(str_map& store) {
int key_nr;
string mapped_value;
string key;
while (store.size() < MAP_SIZE) {
// 0 - 9999
key_nr = rand() % MAP_SIZE;
// convert number to string
mapped_value = to_string(key_nr);
key = "key_" + mapped_value;
pair<string, string> value(key, mapped_value);
store.insert(value);
}
}
void print_map(const str_map& store) {
str_map::const_iterator it = store.begin();
while (it != store.end()) {
pair<string, string> value = *it;
cout << left << setw(10) << value.first << right << setw(5) << value.second << "\n";
it++;
}
}
void search_map(const str_map& store, int thread_nr) {
m.lock();
cout << "thread(" << thread_nr << ") launched\n";
m.unlock();
// use a straight search or poke around random
bool straight = false;
if ((thread_nr % 2) == 0) {
straight = true;
}
int key_nr;
string mapped_value;
string key;
str_map::const_iterator it;
string first;
string second;
for (unsigned int i = 0; i < MAP_SIZE; i++) {
if (straight) {
key_nr = i;
} else {
// 0 - 9999, rand is not thread-safe, nrand48 is an alternative
m.lock();
key_nr = rand() % MAP_SIZE;
m.unlock();
}
// convert number to string
mapped_value = to_string(key_nr);
key = "key_" + mapped_value;
it = store.find(key);
// check result
if (it != store.end()) {
// pair
first = it->first;
second = it->second;
// m.lock();
// cout << "thread(" << thread_nr << ") " << key << ": "
// << right << setw(10) << first << setw(5) << second << "\n";
// m.unlock();
// check mismatch
if (key != first || mapped_value != second) {
m.lock();
cerr << key << ": " << first << second << "\n"
<< "Mismatch in thread(" << thread_nr << ")!\n";
exit(1);
// never reached
m.unlock();
}
} else {
m.lock();
cerr << "Warning: key(" << key << ") not found in thread("
<< thread_nr << ")\n";
exit(1);
// never reached
m.unlock();
}
}
}
int main() {
clock_t start, end;
start = clock();
str_map store;
srand(0);
fill_map(store);
cout << "fill_map finished\n";
// print_map(store);
// cout << "print_map finished\n";
// copy for check
str_map copy_store = store;
// launch threads
thread t[10];
for (int i = 0; i < 10; i++) {
t[i] = thread(search_map, store, i);
}
// wait for finish
for (int i = 0; i < 10; i++) {
t[i].join();
}
cout << "search_map threads finished\n";
if (store == copy_store) {
cout << "equal\n";
} else {
cout << "not equal\n";
}
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
Этот код можно скомпилировать с помощью GCC 4.7 и отлично работает на моей машине.
$echo $?
$ 0