Главная Случайная страница


Полезное:

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


Категории:

АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника






Многопоточное программирование





Введение

Данный материал посвящен многопоточному программированию (multithreading) вообще и многопоточному программированию на C++ в частности. Тема объемная и многоликая, знакомая "C++ кодерам" бесконечной отладкой, безнадежной и непредсказуемой...

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

Многопоточное программирование

Итак, что же такое многопоточное программирование (MultiThreading, MT)? И чем отличается от обычного, однопоточного (Single-Threaded, ST) программирования?

К сожалению, с точки зрения абсолютного большинства MT – то же обычное программирование, разве что щедро усыпанное невообразимым количеством mutex-ов "для корректности". В типичной настольной книжке приведут стандартный пример с двумя потоками (т.е. thread-ами), одновременно изменяющими значение одной и той же переменной, лихо вылечат ошибку посредством mutex-а, а заодно и растолкуют что такое deadlock и почему много mutex-ов тоже плохо.

Итак, что же такое правильное MT приложение? Правильное MT приложение – это, прежде всего, правильный дизайн! Вы никогда не сможете превратить серьезное однопоточное приложение в хорошее многопоточное приложение, сколько бы mutex-ов вы в него не добавили! Многопоточное программирование определяется дизайном приложения, который РАЗРЕШАЕТ параллельное одновременное исполнение.

Не "приложение, исполняющее несколько потоков", а прежде всего "дизайн, разрешающий параллельное исполнение", даже не требующий. Кстати, использование блокировок не разрешает параллельное исполнение, т.е. использование mutex-ов, вообще говоря, не дает возможности получить хороший MT дизайн!

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

Судя по всему, идеальным MT приложением является приложение, в котором потоки вообще избегают какой бы то ни было синхронизации и, следовательно, могут исполняться без всяких взаимных задержек. На первый взгляд такая ситуация кажется абсурдной. Но, если в качестве некоего "логически единого" приложения представить себе два ST Web-сервера, работающих на двух разных машинах и отдающих пользователям один и тот же статический контент из собственных локальных копий, то мы имеем дело как раз с тем самым идеальным случаем, когда добавление второго, абсолютно независимого потока (фактически, запуск на другой машине зеркального сервера) в буквальном смысле удваивает общую производительность комплекса, без оговорок.

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

Описанный дизайн потоки+очередь является классическим примером правильного MT приложения, если конечно потоки не пытаются передавать друг другу настолько малые подзадачи, что операция по помещению/извлечению сообщения из очереди занимает больше времени, чем обработка подзадачи самим потоком "на месте". Дизайн потоки+очередь мы и будем использовать в учебном примере mtftext и в следующих за ним приложениях.

3. Многопоточное программирование на C++

3.1. example1: работа с памятью

Для начала поговорим о подводных камнях C++ применительно к MT программированию. На первый взгляд может показаться, что написанные на C++ потоки, не изменяющие одни и те же данные одновременно, могут работать абсолютно независимо и параллельно. К сожалению, это не совсем так: основной проблемой является стандартный распределитель памяти, т.е. операторы new/delete. Суть проблемы в том, что приложение получает свободные блоки памяти от системы и выдает их части потокам по требованию, т.е. в процессе своей работы потоки вынуждены изменять одни и те же структуры данных (глобальные цепочки свободных блоков) и, следовательно, синхронизация их работы неизбежна.

Прямым решением данной проблемы является повсеместное явное использование собственного распределителя памяти, кэширующего полученные от глобального распределителя блоки. Т.е. своего объекта mem_pool для каждого отдельного потока (как минимум). Конечно, с точки зрения удобства кодирования повсеместное мелькание ссылок mem_pool& трудно назвать приемлемым. Разберем следующий пример:

example1/main.cpp
#include <list>#include <vector>#include <stdio.h>#include <time.h>#include <ders/stl_alloc.hpp>#include <ders/thread.hpp> using namespace std;using namespace ders; const int N=1000;const int M=10000; void start_std(void*){ list<int> lst; for (int i=0; i<N; i++) { for (int j=0; j<M; j++) lst.push_back(j); for (int j=0; j<M; j++) lst.pop_front(); }} void start_ders(void*){ mem_pool mp; stl_allocator<int> alloc(mp); list<int, stl_allocator<int> > lst(alloc); for (int i=0; i<N; i++) { for (int j=0; j<M; j++) lst.push_back(j); for (int j=0; j<M; j++) lst.pop_front(); }} int main(int argc, char** argv){ if (argc!=3) { m1: fprintf(stderr, "main num_threads std|ders"); return 1; } int numThr=atoi(argv[1]); if (!(numThr>=1 && numThr<=100)) { fprintf(stderr, "num_threads must be in [1, 100]"); return 1; } void (*start)(void*); if (strcmp(argv[2], "std")==0) start=start_std; else if (strcmp(argv[2], "ders")==0) start=start_ders; else goto m1; clock_t c1=clock(); mem_pool mp; vector<sh_thread> vthr; for (int i=0; i<numThr; i++) vthr.push_back(new_thread(mp, start, 0)); for (int i=0; i<numThr; i++) vthr[i]->join(); clock_t c2=clock(); printf("%d\t%d\t%s\n", numThr, int(c2-c1), argv[2]); return 0;}

Программа запускает заданное в командной строке количество потоков и в каждом из них выполнят фиксированное количество вставок/удалений элементов в стандартный список. Отличие функции start_ders состоит в том, что вместо стандартного аллокатора по умолчанию lst использует аллокатор на основе mem_pool.

Date: 2015-12-12; view: 1277; Нарушение авторских прав; Помощь в написании работы --> СЮДА...



mydocx.ru - 2015-2024 year. (0.006 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию