![]() |
|
Модераторы: LSD, AntonSaburov |
![]()
|
|
| Pawl |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 649 Регистрация: 22.4.2008 Где: Витебск Репутация: 7 Всего: 28 |
Уважаемые форумчане, столкнулся с задачей, в которой требуется подсчитать количество файлов в каталоге с помощью рекурсии, в нескольких потоках. Я решил воспользоваться шаблоном fork/join, и вот что у меня получилось:
В моем понимании это работает так: сначала просматривается заданный каталог, находящийся по пути path. Если в нем встретится файл, количество файлов (статичесакя переменная с) увеличивается на 1, а если каталог, в список задач попадает новая задача, уже с путем к подкаталогу. Далее эти задачи запускаются, и процесс идет, пока не будет просмотрены все подкаталоги данного каталога. Вроде все прекрасно, и количество файлов считается, но проблема в том, что оно иногда получается немного меньшим, чем есть. Не могу разобраться, почему так происходит, поэтому был бы рад, если кто-нибудь сможет это объяснить. Спасибо! -------------------- В действительности всё совсем не так, как на самом деле |
|||
|
||||
| jk1 |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Сообщений: 1168 Регистрация: 17.10.2008 Где: Санкт-Петербург Репутация: 40 Всего: 75 |
Имхо все довольно прозаично: статическая переменная, доступ к ней не синхронизирован никак, запись конкурентная из нескольких исполняющихся задач. А инкремент, как известно, не атомарный.
Вообще FJF используется очень странно: fork отсутсвует, join тоже. Идея у этого фреймворка во многом похожа на сортировку слиянием: результаты рекурсивных вызовов нужно мержить (в вашем случае - числа складывать) в вызывающем контексте. А никак не копить в статической переменной. Собирая все это вместе получаем примерно следующее:
Добавлю, что это не самый эффективный по производительности вариант, но самый простой и для иллюстрации правильного использования подойдет. -------------------- Opinions are like assholes — everybody has one |
|||
|
||||
| Pawl |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 649 Регистрация: 22.4.2008 Где: Витебск Репутация: 7 Всего: 28 |
Спасибо за объяснение и наглядный пример. Свой я делал по образцу из книги "Beginning Java 7" by Jeff Friesen, Listing 6-9. Multiplying two matrixes via the Fork/Join Framework. Там также вместо fork и join использовался invokeAll. Как я понял, этот метод объединяет в себе 2 предыдущих:
Вот как. Теперь и мне это известно! И все-же мне непонятно, почему при отсутствии синхронизации файлы именно недосчитываются? Я понимаю, если бы выводилось число большее, чем реальное количество файлов: к примеру, один поток может увеличить с дважды, а тут... получается, что в некоторых задачах в методе compute ветка else вообще никогда не задействуется? Если не трудно, можно об этом поподробнее, пожалуйста. -------------------- В действительности всё совсем не так, как на самом деле |
|||
|
||||
| jk1 |
|
||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Сообщений: 1168 Регистрация: 17.10.2008 Где: Санкт-Петербург Репутация: 40 Всего: 75 |
Объединяет, только вот API у него неудобное для возвращения результатов. Правильная реализация на нем выглядит примерно так:
Как можно видеть результаты потом можно получить только через стандартный Future'овский get() со всеми его исключениями.
С точностью до наоборот. Насчитать больше реального он не может (с чего бы одному потоку прибавлять дважды? в коде явно указан один инкремент на таск), а насчитать меньше - пожалуйста. Пусть у нас есть два потока, А и B. Инкремент состоит из операции чтения и операции записи увеличенного значения. Тогда реальная последовательность операций на железе может быть такой: (текущее значение счетчика = 0) А: читает текущее значение 0 B: читает текущее значение 0 A: увеличивает считанное значение до 1 и выполняет запись (текущее значение счетчика теперь = 1. Поток B может видеть это, может не видеть, но это неважно - он уже все считал ранее) В: увеличивает ранее считанный 0 до 1 и выполняет запись (текущее значение счетчика = 1) В итоге в обоих потоках отработал инкремент, но в счетчике лежит не 2, а 1. -------------------- Opinions are like assholes — everybody has one |
||||||
|
|||||||
| Pawl |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 649 Регистрация: 22.4.2008 Где: Витебск Репутация: 7 Всего: 28 |
Действительно, весьма доступно, спасибо! Оффтоп: Кажется, Эйнштейн говорил, что если вы в чем-то действительно разбираетесь, то сможете объяснить это своей бабушке. Так вот, думаю, Вы сможете А по теме, я тут проделал эксперимент (для общего развития) - объявил с как private static AtomicInteger c = new AtomicInteger(), вместо ++с написал c.incrementAndGet() и вернул c.intValue(). Результат выдает всегда правильный. Кстати, о развитии. Хочу все-же FJF лучше освоить, поэтому, если можете подкинуть несколько типовых задач или подсказать, где их взять, был бы очень благодарен. -------------------- В действительности всё совсем не так, как на самом деле |
|||
|
||||
| jk1 |
|
||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Сообщений: 1168 Регистрация: 17.10.2008 Где: Санкт-Петербург Репутация: 40 Всего: 75 |
Спасибо
Задач можно придумать много, например merge sort написать на FJF или программу, выкачивающую на локальную машину все страницы сайта вместе с ассоциированными ресурсами. Но если Вы хотите научиться с пользой применять FJF на практике, то следует подумать вот о чем: распараллеливание несет с собой накладные расходы на управление очередями задач и переключение контекста. Чем больше задач, тем больше overhead. В Вашем примере с подсчетом файлов конкретная задача на исполнение очень мелкая, по сути она сводится к return 1. Поэтому такая реализация будет медленнее даже однопоточного линейного обхода. Чтобы действительно выигрывать в производительности необходимо уметь правильно выбирать размер элементарной задачи - задачи, которая будет выполнятся непосредственно без дальнейшего разбиения. Если выбрать её слишком большой, то ядра будут простаивать. Если выбрать её слишком маленькой, то основное время будет уходить на жонглирование потоками и задачами, а не на полезную работу. -------------------- Opinions are like assholes — everybody has one |
||||
|
|||||
| Pawl |
|
||||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 649 Регистрация: 22.4.2008 Где: Витебск Репутация: 7 Всего: 28 |
Конечно, именно применять и именно с пользой!
Понятно, просто пока набиваю руку. Вот, некоторые концепции схватываю сразу, а некоторые - хуже. Чувствую, тут я пока толком не въехал, поэтому и пытаюсь применить FJF ко всему подряд. имеется ввиду сортировка слиянием?
Вот это интересная задача - похожа на ту, что я тут привел, только задачей на исполнение будет выкачка ресурсов. -------------------- В действительности всё совсем не так, как на самом деле |
||||
|
|||||
![]()
|
| Правила форума "Java" | |
|
|
Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic. |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | Java: Общие вопросы | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |