Циркулярный буфер с Threads Consumer и Producer: он зависает при выполнении некоторых программ
Я разрабатываю кольцевой буфер с двумя потоками: потребитель и производитель. Я использую активное ожидание с Thread.yield
, Я знаю, что это можно сделать с семафорами, но я хотел буфер без семафоров.
Оба имеют общую переменную: bufferCircular
,
Хотя буфер не полон полезной информации, producer
написать данные в позиции p
массива, и в то время как есть некоторая полезная информация consumer
читать данные в позиции c
массива. Переменная nElem
от BufferCircular
количество значений данных, которые еще не были прочитаны
Программа работает неплохо, работает 9/10 раз. Затем, иногда, он застревает в бесконечном цикле, прежде чем показывать последний элемент на экране (номер 500 цикла для), или просто не показывает какой-либо элемент.
Я думаю, что это, вероятно, liveLock, но я не могу найти ошибку.
Общая переменная:
public class BufferCircular {
volatile int[] array;
volatile int p;
volatile int c;
volatile int nElem;
public BufferCircular(int[] array) {
this.array = array;
this.p = 0;
this.c = 0;
this.nElem = 0;
}
public void writeData (int data) {
this.array[p] = data;
this.p = (p + 1) % array.length;
this.nElem++;
}
public int readData() {
int data = array[c];
this.c = (c + 1) % array.length;
this.nElem--;
return data;
}
}
Автор темы:
public class Producer extends Thread {
BufferCircular buffer;
int bufferTam;
int contData;
public Productor(BufferCircular buff) {
this.buffer = buff;
this.bufferTam = buffer.array.length;
this.contData = 0;
}
public void produceData() {
this.contData++;
this.buffer.writeData(contData);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == this.bufferTam) {
Thread.yield();
}
this.produceData();
}
}
}
Потребительская нить:
public class Consumer extends Thread {
BufferCircular buffer;
int cont;
public Consumer(BufferCircular buff) {
this.buffer = buff;
this.cont = 0;
}
public void consumeData() {
int data = buffer.readData();
cont++;
System.out.println("data " + cont + ": " + data);
}
public void run() {
for (int i = 0; i < 500; i++) {
while (this.buffer.nElem == 0) {
Thread.yield();
}
this.consumeData();
}
}
}
Основное:
public class Main {
public static void main(String[] args) {
Random ran = new Random();
int tamArray = ran.nextInt(21) + 1;
int[] array = new int[tamArray];
BufferCircular buffer = new BufferCircular(array);
Producer producer = new Producer (buffer);
Consumer consumer = new Consumer (buffer);
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
System.err.println("Error with Threads");
e.printStackTrace();
}
}
}
Любая помощь будет приветствоваться.
1 ответ
Ваша проблема здесь в том, что ваш BufferCircular
методы чувствительны к условиям гонки. Возьмите например writeData()
, Он выполняется в 3 этапа, некоторые из которых также не являются атомарными:
this.array[p] = data; // 1
this.p = (p + 1) % array.length; // 2 not atomic
this.nElem++; // 3 not atomic
Предположим, что 2 темы введены writeData()
в то же время. На шаге 1 они оба имеют одинаковые p
значение, и оба переписать array[p]
значение. Сейчас, array[p]
перезаписывается дважды, и данные, которые должен был записать первый поток, теряются, потому что второй поток записывал в тот же индекс после. Затем они выполняют шаг 2- и результат непредсказуем, поскольку р можно увеличить на 1 или 2 (p = (p + 1) % array.length
состоит из 3 операций, где потоки могут взаимодействовать). Затем шаг 3. ++
Оператор тоже не атомарный: он использует 2 операции за кулисами. Так nElem
также увеличивается на 1 или 2.
Так что у нас совершенно непредсказуемый результат. Что приводит к плохому исполнению вашей программы.
Самое простое решение - сделать readData()
а также writeData()
методы сериализованы. Для этого объявить их synchronized
:
public synchronized void writeData (int data) { //...
public synchronized void readData () { //...
Если у вас только один производитель и один потребительский поток, могут возникнуть условия гонки на операциях, связанных с nElem
, Решение заключается в использовании AtomicInteger
вместо int
:
final AtomicInteger nElem = new AtomicInteger();
и использовать его incrementAndGet()
а также decrementAndGet()
методы.