оптимизация java производитель-потребитель - PullRequest
1 голос
/ 07 апреля 2011

Я пишу приложение, которое рисует определенные фигуры.

Прежде чем рисовать каждую фигуру, необходимо выполнить некоторые вычисления.Вот почему у меня есть еще один поток для рисования уже обработанных элементов.

Для синхронизации между двумя потоками я использовал семафор.Прежде чем ящик попытается извлечь элемент из очереди, он получит разрешение, и каждый раз, когда элемент ставится в очередь, он будет выдавать разрешение.Кроме того, методы enqueue и dequeue синхронизированы.вычислительная нить будет ждать окончания работы выдвижного ящика, если количество разрешений не равно нулю к моменту выполнения вычислительной нити.

РЕДАКТИРОВАТЬ Синхронизированный блок в вычислительной нити долженне допускать продолжения вычислительной нити до завершения рисования.Если бы это произошло, это могло бы испортить ситуацию.

После профилирования выдача разрешений занимает 8.3% времени выполнения вычислительного потока.Что приемлемо.Проблема с рисунком темы.Метод получения занимает 63% времени выполнения.

Кроме того, я попытался использовать очередь блокировки, но это было еще хуже.

код:

поток рисования:

private void drawNext(){
    QueuedDraw item;
    if(drawingStatus.availablePermits()==0)
        synchronized(drawingStatus){
            if(drawingStatus.availablePermits()==0)
                drawingStatus.notify();
        }
    try {
        drawingStatus.acquire();
    } catch (InterruptedException ex) {
        Logger.getLogger(PolygonDrawer.class.getName())
                             .log(Level.SEVERE, null, ex);
    }
    item = queue.dequeue();
    fillPoly(item.points, item.color, item.vertices);

}    

public final void fillSquare(Point[] points, byte[] color){
    queue.enqueue(new QueuedDraw(points, color, SQUARE_VERTICES));
    drawingStatus.release();
}
public final void fillTriangle(Point[] points, byte[] color){
    queue.enqueue(new QueuedDraw(points, color, TRIANGLE_VERTICES));
    drawingStatus.release();
}

@Override
public void run() {
    while(true){
        drawNext();
    }
}

поток вычислений:

примечание: перед этим блоком находятся все вызовы методов, которые вызывают fillSquare /fillTriangle

if(paintStatus.availablePermits()!=0)
        synchronized(paintStatus){
            try {
                if(paintStatus.availablePermits()!=0)
                    paintStatus.wait();
            } catch (InterruptedException ex) {
                Logger.getLogger(Painter.class.getName())
                                 .log(Level.SEVERE, null, ex);
            }
        }

Ответы [ 2 ]

3 голосов
/ 07 апреля 2011

В вашей текущей реализации кажется, что вы заставляете потоки работать в режиме lockstep. Если ваша очередь является поточно-ориентированной, такой как ArrayBlockingQueue , вам не требуется явная синхронизация, предоставляемая семафором. Как только вычислительный поток завершит обновление элемента, он может просто поместить его в очередь. Вычислительный поток может помещать элементы в очередь одновременно с тем, что поток рисования извлекает их. В принципе, у вас может быть несколько вычислительных потоков, выполняющих вычисления параллельно. Поток чертежа использует poll () для извлечения элементов из очереди, что блокирует поток, если очередь пуста.

Это сделает код чище и больше полагается на установленные классы. Если накладные расходы на синхронизацию все еще слишком велики, вы можете выполнять вычисления в пакетах и ​​добавлять пакет вычислений в очередь как один элемент. Тип очереди будет (скажем) List<QueuedDraw>.

РЕДАКТИРОВАТЬ: стоимость ожидания может быть значительной (как вы видели), если вычисления сравнительно быстрые по сравнению со стоимостью блокировки, поэтому очередь блокировки здесь не идеальна. Вместо этого используйте очередь с алгоритмом «без ожидания», таким как ConcurrentLinkedQueue . Этот тип очереди не блокирует потоки с помощью планировщика, а вместо этого использует атомарные операции для защиты доступа к общему состоянию.

Помните, что ваша нить для рисования может рисовать только так быстро, как вы рассчитываете. Может иметь смысл, что он тратит много времени на ожидание появления предметов, если для их вычисления требуется больше времени, чем для рисования. Распараллеливание вычислений поможет продвинуть поток рисования.

0 голосов
/ 07 апреля 2011

Используйте BlockingQueue.take и BlockingQueus.put.

Причина, по которой эти или acquire будут показывать большую долю затраченного времени, заключается в (наиболее вероятном) блокировании этих методов, когда ничего не доступно или очередь заполнена (если она может быть заполнена).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...