В настоящее время я пытаюсь решить проблему безопасности потоков с помощью контейнеров C ++ STL. Недавно я попытался реализовать потокобезопасный std :: vector, используя std :: mutex в качестве переменной-члена, просто чтобы потом понять, что, хотя я мог сделать функции-члены поточно-безопасными, блокируя блокировку, я не смог сделать функции lib как std :: sort потокобезопасен, так как они получают только итераторы begin () / end (), что является результатом фундаментального разделения между контейнерами и алгоритмами в STL в целом.
Тогда я подумал: если я не могу использовать блокировки, как насчет программной транзакционной памяти (STM)?
Так что теперь я застрял с этим:
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <vector>
#define LIMIT 10
std::atomic<bool> start{false};
std::vector<char> vec;
void thread(char c)
{
while (!start)
std::this_thread::yield();
for (int i = 0; i < LIMIT; ++i) {
__transaction_atomic {
vec.push_back(c);
}
}
}
int main()
{
std::thread t1(thread, '*');
std::thread t2(thread, '@');
start.store(true);
t1.join();
t2.join();
for (auto i = vec.begin(); i != vec.end(); ++i)
std::cout << *i;
std::cout << std::endl;
return EXIT_SUCCESS;
}
Который я собираю с:
g++ -std=c++11 -fgnu-tm -Wall
используя g ++ 4.8.2 и который дает мне следующую ошибку:
error: unsafe function call to push_back within atomic transaction
Что я вроде получаю, так как push_back или sort, или что-то еще, не объявленоaction_safe, но которое оставляет меня со следующими вопросами:
а) Как я могу исправить эту ошибку?
б) Если я не могу исправить эту ошибку, то для чего обычно используются эти транзакционные блоки?
в) Как можно реализовать свободный от блокировок потокобезопасный вектор ?!
Заранее спасибо!
Редактировать:
Спасибо за ответы до сих пор, но они действительно не чешут мой зуд. Позволь мне привести пример:
Представьте, что у меня есть глобальный вектор, и доступ к этому вектору должен быть общим для нескольких потоков. Все потоки пытаются сделать отсортированные вставки, поэтому они генерируют случайное число и пытаются вставить это число в вектор отсортированным образом, чтобы вектор оставался отсортированным все время (включая дубликаты c). Чтобы выполнить отсортированную вставку, они используют std :: lower_bound, чтобы найти «индекс», куда вставить, а затем выполняют вставку, используя vector.insert ().
Если я напишу оболочку для std :: vector, которая содержит std :: mutex в качестве члена, то я могу написать функции-оболочки, например, insert, который блокирует мьютекс с помощью std :: lock_guard, а затем выполняет фактический вызов std :: vector.insert (). Но std :: lower_bound не имеет никакого значения для мьютекса члена. Что является особенностью, а не ошибкой.
Это приводит к тому, что мои потоки находятся в довольно затруднительном положении, потому что другие потоки могут изменять вектор, пока кто-то делает его нижнюю привязку.
Единственное исправление, о котором я могу подумать: забыть обертку и вместо этого иметь глобальный мьютекс для вектора. Всякий раз, когда кто-то хочет сделать что-либо с / с / с этим вектором, ему нужен этот замок.
Это проблема. Какие есть альтернативы для использования этого глобального мьютекса.
и то, где программное обеспечение транзакционной памяти пришло на ум.
Итак, теперь: как использовать STM в контейнерах STL? (и а), б), в) сверху).
Я считаю, что единственный способ сделать контейнер STL на 100% потокобезопасным — это обернуть его в свой собственный объект (сохраняя фактический контейнер закрытым) и использовать соответствующие блокировки (мьютексы, что угодно) в вашем объекте, чтобы предотвратить многопоточность доступ к контейнеру STL.
Это моральный эквивалент просто блокировки мьютекса в вызывающей стороне вокруг каждой операции контейнера.
Чтобы сделать контейнер действительно потокобезопасным, вам придется поработать с кодом контейнера, для которого нет никаких условий.
Изменить: еще одно замечание — будьте осторожны с интерфейсом, который вы даете объекту-оболочке. Вы не можете хорошо раздавать ссылки на хранимые объекты, так как это позволит вызывающей стороне обойти блокировку оболочки. Таким образом, вы не можете просто дублировать интерфейс вектора с мьютексами и ожидать, что все будет работать.
Я не уверен, что понимаю, почему нельзя использовать мьютексы. Если вы блокируете мьютекс каждый раз, когда получаете доступ к вектору, то независимо от того, какую операцию вы выполняете, вы уверены, что за один раз его использует только один поток. Конечно, есть место для улучшения в зависимости от ваших потребностей в безопасном векторе, но мьютексы должны быть совершенно жизнеспособными.
заблокировать мьютекс -> вызвать std :: sort или что вам нужно -> разблокировать мьютекс
Если, с другой стороны, вы хотите использовать std :: sort в своем классе, то опять же, это вопрос обеспечения потокового доступа и методов чтения через итераторы вашего контейнера, так как это те, которые std :: В любом случае для сортировки вектора нужно использовать sort, поскольку он не является другом контейнеров или чего-либо в этом роде.
Вы можете использовать простые мьютексы, чтобы сделать ваш класс безопасным. Как указано в другом ответе, вам нужно использовать мьютекс, чтобы заблокировать вектор перед использованием, а затем разблокировать после использования.
ВНИМАНИЕ! Все функции STL могут генерировать исключения. Если вы используете простые мьютексы, у вас возникнет проблема, если какая-либо функция сгенерирует, потому что мьютекс не будет освобожден. Чтобы избежать этой проблемы, поместите мьютекс в класс, который освобождает его в деструкторе. Это хорошая практика программирования, чтобы узнать о: http://c2.com/cgi/wiki?ResourceAcquisitionIsInitialization