книги хакеры / журнал хакер / 174_Optimized
.pdf
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
C |
|
E |
|
|||
|
|
X |
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
||
|
F |
|
|
|
|
|
|
t |
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
r |
|
P |
|
|
|
|
|
NOW! |
o |
||
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|||
|
|
|
|
to |
110 m |
||||
w Click |
|
||||||||
|
|
||||||||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
. |
|
|
|
|
|
.c |
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
df |
|
|
n |
e |
||
|
|
|
|
-xcha |
|
|
|
Кодинг
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
ХАКЕР 07 /174/ 2013 |
|
|
|
|
|
|
||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
ПРАВИЛЬНАЯ
МНОГОПОТОЧНОСТЬ
«Да» — плавности, «нет» — блокировкам!
Известно, что приложения бывают однопоточные и многопоточные. Single thread софт кодить легко и приятно: разработчику не надо задумываться о синхронизации доступа, блокировках, взаимодействии между нитями и так далее. В многопоточной среде все эти проблемы часто становятся кошмаром для программиста, особенно если опыта работы с тредами у него еще не было. Чтобы облегчить себе жизнь в будущем и сделать multi thread приложение надежным и производительным, нужно заранее продумать, как будет устроена работа с потоками. Эта статья поможет твоему мозгу увидеть правильное направление!
Deeonis deeonis@gmail.com
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
|
||
P |
|
|
|
|
|
NOW! |
o |
|
|
||
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
|
w Click |
|
ХАКЕР m |
07 /174/ 2013 |
Правильная многопоточность |
|||||||
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
. |
|
|
|
|
|
.c |
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
|
||
|
|
|
|
-xcha |
|
|
|
|
|
Аты. Пользователи любят, когда софт выполняет все действия практически моментально, а UI работает
плавно и без лагов. Как бы ни росла производительность железа, но соответствовать таким высокимсинхронное программирование набирает оборо-
требованиям можно только с помощью тредов и асинхронности, а это, в свою очередь, создает множество мелких и не очень проблем, которые придется решать обычным программистам.
Для того чтобы понять, что за подводные камни таит в себе работа в многопоточной среде, нужно взглянуть на следующий код:
Singlethread код int Foo()
{
int res;
// Что-то долго делаем и возвращаем результат retrun res;
}
// В основном потоке вызываем Foo auto x = Foo();
// ...
У нас есть функция Foo, которая выполняет некоторые дей- |
|
ствия и возвращает результат. В главном потоке программы мы |
|
запускаем ее, получаем результат работы и идем делать даль- |
|
ше свои дела. Все хорошо, за исключением того, что выполне- |
|
ние Foo занимает довольно длительное время и ее вызов в GUI- |
|
потоке приведет к замерзанию всего интерфейса. |
|
Это плохо, очень плохо. Современный юзер такого не пере- |
Пример использования |
живет. Поэтому мы, немного подумав, решаем вынести дей- |
std::thread |
ствия, выполняемые в нашей функции, в отдельный поток. GUI |
|
не залипнет, а у Foo появится возможность спокойно отрабо- |
|
тать в своем треде. |
|
АСИНХРОННЫЙ ВЫЗОВ ФУНКЦИИ
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
|
X |
|
|
|
|
|
|||
|
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
to |
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
m |
||
111Click |
|
|
|
|
|
||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
|
-x cha |
|
|
|
|
std::future<int> f = std::async(std::launch:: async, Foo);
//Работаем дальше в основном потоке
//Когда нам нужно, получаем результат работы Foo auto x = f.get();
С выходом C++11 жить стало проще. Теперь для создания своего треда не надо использовать сложные API Майкрософт или вызывать устаревшую _beginthread. В новом стандарте появилась нативная поддержка работы с потоками. В частности, сейчас нас интересует класс std::thread, который является не чем иным, как STL представлением потоков. Работать с ним — одно удовольствие, для запуска своего кода достаточно лишь передать его в конструктор std::thread в виде функционального объекта, и можно наслаждаться результатом.
Стоит также отметить, что мы можем дождаться, когда поток закончит свою работу. Для этого нам пригодится метод thread::join, который как раз и служит для этих целей. А можно и вовсе не дожидаться, сделав thread::detach. Наш предыдущий однопоточный пример может быть преобразован в multi thread всего лишь добавлением одной строки кода.
В std::async мы передали флаг std::launch::async, который означает, что код надо запустить в отдельном потоке, а также нашу функцию Foo. В результате мы получаем объект std::future. После чего мы опять продолжаем заниматься своими делами и, когда нам это понадобится, обращаемся за результатом выполнения Foo к переменной f, вызывая метод future::get.
Выглядит все идеально, но опытный программист наверняка задаст вопрос: «А что будет, если на момент вызова future::get функция Foo еще не успеет вернуть результат своих действий?» А будет то, что главный поток остановится на вызове GET до тех пор, пока асинхронный код не завершится.
Таким образом, при использовании std::future главный поток может заблокироваться, а может и нет. В итоге мы получим нерегулярные лаги нашего GUI. Наша цель — полностью избежатьтаких блокировок и добиться того, чтобы главный тред работал быстро и без фризов.
Многопоточность с помощью std::thread |
|
// ... |
CONCURRENT QUEUE |
auto thread = std::thread(Foo); |
В примере с future::get мы фактически использовали мью- |
// Foo выполняется в отдельном потоке |
текс. Во время попытки получения значения из std::future код |
// ... |
шаблонного класса проверял, закончил ли свою работу поток, |
|
запущенный с помощью std::async, и если нет, то ожидал его |
Казалось бы, все хорошо. Мы запустили Foo в отдельном по- |
завершения. Для того чтобы один поток никогда не ждал, пока |
токе, и, пока она отрабатывает, мы спокойно занимаемся сво- |
отработает другой, умные программисты придумали потокобе- |
ими делами, не заставляя основной поток ждать завершения |
зопасную очередь. |
длительной операции. Но есть одно но. Мы забыли, что Foo воз- |
Любой кодер знает такие структуры данных, как вектор, мас- |
вращает некий результат, который, как ни странно, нам нужен. |
сив, стек и так далее. Очередь — это одна из разновидностей |
Самые смелые могут попробовать сохранять результат в какую- |
контейнеров, работающая по принципу FIFO (First In First Out). |
нибудь глобальную переменную, но это не наш метод — слиш- |
Thread-safe очередь отличается от обычной тем, что добавлять |
ком непрофессионально. |
и удалять элементы можно из разных потоков и при этом не бо- |
Специально для таких случаев в STL есть замечательные |
яться, что мы одновременно попробуем записать или удалить |
std::async и std::future — шаблонная функция и класс, которые |
что-нибудь из очереди, тем самым с большой долей вероятно- |
позволяют запустить код асинхронно и получить по запросу |
сти получив падение программы или, что еще хуже, неопреде- |
результат его работы. Если переписать предыдущий пример |
ленное поведение. |
с использованием новых примитивов, то мы получим прибли- |
Concurrent queue можно использовать для безопасного вы- |
зительно следующее: |
полнения кода в отдельном потоке. Выглядит это примерно так: |
|
в потокобезопасную очередь мы кладем функциональный объ- |
Пробуем std::async и std::future |
ект, а в это время в рабочем потоке крутится бесконечный цикл, |
// ... |
который на каждой итерации обращается к очереди, достает |