Я экспериментирую с многопоточностью в Java, точнее, с потоками. В качестве теста я написал приложение, которое просто меняет цвет изображения, используя многопоточность для скорости. Однако по неизвестной мне причине я получаю искаженные результаты в зависимости от того, как я настроил этот тест. Ниже я опишу, как тестовое приложение работает вместе с полным исходным кодом.
Любая помощь очень приветствуется! Спасибо!
Тестовое приложение
У меня есть буфер изображения размером 400x300 пикселей, инициализированный синим цветом, как показано ниже:
Программа должна полностью заполнить его красным цветом.
Хотя я мог бы просто зациклить все пиксели, покрасив каждый последовательно красным, яДля производительности решил воспользоваться преимуществами параллелизма. Таким образом, я решил заполнить каждую строку изображения отдельным потоком. Поскольку количество строк (300 строк) намного превышает количество доступных ядер ЦП, я создал пул потоков (содержащий 4 потока), который будет выполнять 300 задач (каждая из которых отвечает за заполнение одной строки).
Программа организована следующим образом:
- Класс RGB: содержит цвет пикселя в трех кортежах.
- Класс RenderTask: заполняет данную строкубуфер изображения с красным цветом.
- Класс рендерера:
- создает буфер изображения.
- создает пул потоков с помощью "newFixedThreadPool".
- создает 300 задач, которые будут использоваться пулом потоков.
- завершает службу пула потоков.
- записывает буфер изображения в файл PPM.
Ниже вы можете найти полный исходный код (Я назову этот код Версия 1 ):
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.io.*;
class RGB {
RGB() {}
RGB(double r, double g, double b) {
this.r = r;
this.g = g;
this.b = b;
}
double r;
double g;
double b;
}
class RenderTask implements Runnable {
RenderTask(RGB[][] image_buffer, int row_width, int current_row) {
this.image_buffer = image_buffer;
this.row_width = row_width;
this.current_row = current_row;
}
@Override
public void run() {
for(int column = 0; column < row_width; ++column) {
image_buffer[current_row][column] = new RGB(1.0, 0.0, 0.0);
}
}
RGB[][] image_buffer;
int row_width;
int current_row;
}
public class Renderer {
public static void main(String[] str) {
int image_width = 400;
int image_height = 300;
// Creates a 400x300 pixel image buffer, where each pixel is RGB triple of doubles,
// and initializes the image buffer with a dark blue color.
RGB[][] image_buffer = new RGB[image_height][image_width];
for(int row = 0; row < image_height; ++row)
for(int column = 0; column < image_width; ++column)
image_buffer[row][column] = new RGB(0.0, 0.0, 0.2); // dark blue
// Creates a threadpool containing four threads
ExecutorService executor_service = Executors.newFixedThreadPool(4);
// Creates 300 tasks to be consumed by the threadpool:
// Each task will be in charge of filling one line of the image buffer.
for(int row = 0; row < image_height; ++row)
executor_service.submit(new RenderTask(image_buffer, image_width, row));
executor_service.shutdown();
// Saves the image buffer to a PPM file in ASCII format
try (FileWriter fwriter = new FileWriter("image.ppm");
BufferedWriter bwriter = new BufferedWriter(fwriter)) {
bwriter.write("P3\n" + image_width + " " + image_height + "\n" + 255 + "\n");
for(int row = 0; row < image_height; ++row)
for(int column = 0; column < image_width; ++column) {
int r = (int) (image_buffer[row][column].r * 255.0);
int g = (int) (image_buffer[row][column].g * 255.0);
int b = (int) (image_buffer[row][column].b * 255.0);
bwriter.write(r + " " + g + " " + b + " ");
}
} catch (IOException e) {
System.err.format("IOException: %s%n", e);
}
}
}
Кажется, все работает с этим кодом, и я получаю ожидаемый красный буфер изображения, как показано ниже:
Проблема
Однако, если я изменю метод RenderTask.run () так, чтобы он переустанавливался с избыточностьюцвет одной и той же позиции буфера несколько раз в последовательности, как показано ниже (я назову это Версия 2 ):
@Override
public void run() {
for(int column = 0; column < row_width; ++column) {
for(int s = 0; s < 256; ++s) {
image_buffer[current_row][column] = new RGB(1.0, 0.0, 0.0);
}
}
}
Затем я получаю следующий поврежденный буфер изображения:
На самом деле, результат меняется каждый раз, когда я запускаю программу, новсегда поврежден.
Насколько я понимаю, нет двух потоков, которые одновременно пишут в одну и ту же позицию памяти, поэтому кажется, что в поле зрения нет гоночных условий.
Даже в случае «ложного обмена», который, как я думаю, не происходит, я ожидал бы только более низкую производительность, а не искаженные результаты.
Таким образом, даже при избыточных назначениях яожидал получить правильный результат (т.е. полностью красный буфер изображения).
Итак, мои вопросы: почему это происходит с версией 2 программы, если единственное отличие от версии 1 состоит в том, что операция присваивания выполняется избыточно в рамках потока?
Было бы так, что некоторые потоки уничтожаются до того, как они закончат работу? Это будет ошибка в JVM? Или я пропустил что-то тривиальное? (самая сильная гипотеза:)
Спасибо, ребята !!