Я пытаюсь синхронизировать потоки с переменными состояния для вывода мандельброта, но получаю неправильный мандельборт.
Функции output_mandel_line
и compute_mandel_line
заданы и являются правильными. я сделал
void *function()
и int main()
. Я подозреваю, что проблема связана с оператором if внутри mutex_lock
. Я попробовал другой оператор if или while, но не смог это исправить. Возможно, есть проблема и в том, как я использую cond_wait
,cond_broadcast
.
int nthreads;
pthread_cond_t cond;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/***************************
* Compile-time parameters *
***************************/
/*
* Output at the terminal is is x_chars wide by y_chars long
*/
int y_chars = 50;
int x_chars = 90;
/*
* The part of the complex plane to be drawn:
* upper left corner is (xmin, ymax), lower right corner is (xmax, ymin)
*/
double xmin = -1.8,
xmax = 1.0;
double ymin = -1.0,
ymax = 1.0;
/*
* Every character in the final output is
* xstep x ystep units wide on the complex plane.
*/
double xstep;
double ystep;
/*
* This function computes a line of output
* as an array of x_char color values.
*/
void
compute_mandel_line(int line, int color_val[])
{
}
/*
* This function outputs an array of x_char color values
* to a 256-color xterm.
*/
void
output_mandel_line(int fd, int color_val[])
{
}
/* Now that the line is done, output a newline character */
if (write(fd, &newline, 1) != 1) {
perror("compute_and_output_mandel_line: write newline");
exit(1);
}
}
void *
function(void *arg)
{
int *number = (int *) arg,
line;
int color_val[x_chars];
for (line = *number; line < y_chars; line += nthreads) {
compute_mandel_line(line, color_val);
pthread_mutex_lock(&mutex);
output_mandel_line(1, color_val);
pthread_cond_signal(&cond);
if (line + nthreads < y_chars) {
pthread_cond_wait(&cond, &mutex);
}
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
free(arg);
return NULL;
}
int
main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Wrong format");
exit(1);
}
nthreads = atoi(argv[1]);
int i;
pthread_t t[nthreads];
// pthread_t *t =(pthread_t*)malloc(nthreads * sizeof(pthread_t));
// ret =(pthread_cond_t*)malloc(nthreads * sizeof(pthread_cond_t));
int ret;
xstep = (xmax - xmin) / x_chars;
ystep = (ymax - ymin) / y_chars;
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
for (i = 0; i < nthreads; i++) {
int *a = malloc(sizeof(int));
*a = i;
ret = pthread_create(&t[i], NULL, function, a);
if (ret) {
perror_pthread(ret, "error");
exit(1);
}
}
for (i = 0; i < nthreads; i++) {
ret = pthread_join(t[i], NULL);
if (ret) {
perror_pthread(ret, "error");
exit(1);
}
}
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
/*
* Allocate memory for threads and semaphores
* draw the Mandelbrot Set, one line at a time.
* Output is sent to file descriptor '1', i.e., standard output.
*/
reset_xterm_color(1);
return 0;
}
Я попытался изменить оператор if и порядок условных переменных cond_wait
, cond_signal
.
Первый вопрос, на который нужно ответить, когда вы выполняете ожидание условной переменной, — какое условие вы ожидаете? Вы можете использовать одно и то же CV в нескольких потоках или несколько раз, чтобы дождаться, пока различные условия станут истинными, но если нет выражения, которое вы можете проверить, чтобы определить, стоит ли ждать (или ждать больше), тогда вы используете свое CV. неправильно.
А поскольку вам нужно проверить условие после того, как вы вернетесь из ожидания, и, возможно, подождать еще немного, идиомы для правильного использования CV в основном выполняют ожидание внутри цикла.
@CraigEstey, сигналы CV не ставятся в очередь и не сохраняются, ни один. (Они не являются «сигналами» в смысле функции kill()
.) Только поток, уже заблокированный в pthread_cond_wait()
, может быть разбужен вызовом pthread_cond_signal()
. Ни один поток не может вырваться из своего состояния pthread_cond_wait()
, независимо от того, какой мьютекс он удерживает и когда.
Ваша цель здесь, по-видимому, состоит в том, чтобы распределить вычисления по нескольким потокам, гарантируя при этом, что они записывают свои выходные данные в правильном порядке. В этом может помочь условная переменная, хотя это не единственный возможный подход. На самом деле, вероятно, было бы проще настроить семафор для каждого потока, чтобы уведомлять его о своей очереди вывода.
Но если вы хотите использовать резюме, важно понимать модель использования:
Цель CV — позволить потоку при необходимости приостановить работу до тех пор, пока какое-либо условие не станет истинным в результате действия другого потока. Если нет условий для проверки потока, чтобы определить, стоит ли продолжать, то CV — неправильный инструмент для этой работы.
Поток, достигающий точки ожидания, опосредованной CV, должен сначала проверить, истинно ли условие для его продолжения. Если да, то вообще не ждет.
Если поток выполняет ожидание CV, то после разблокировки он должен еще раз проверить, удовлетворено ли условие (a) для продолжения работы. Это предполагает несколько возможностей:
что CV было сообщено по какой-то другой причине, кроме конкретного условия, изменения которого ожидал поток
что условие, хотя, возможно, и было истинным во время сигнала CV, снова становится ложным к тому времени, когда поток получает возможность запуститься
что поток проснулся внезапно, без отправки сигнала в CV вообще.
Обычно ожидающий поток возвращается в режим ожидания, если его условия продолжения не выполняются.
Не случайно CV используются вместе с мьютексами. Поток, ожидающий CV, должен наблюдать за влиянием одного или нескольких других потоков на некоторую часть (общих) данных, которые делают условие истинным, которое ранее было ложным. Чтобы избежать гонки данных вокруг этих данных, требуется синхронизация, которая (если все сделано правильно) обеспечивается мьютексом.
Очевидно, вы хотите, чтобы ваши потоки работали по очереди и записывали свои выходные данные в циклическом порядке. В этом случае условием, которого хочет дождаться каждый поток, будет, по-английски, «это моя очередь». Чтобы поток мог оценить это вычислительно, должна быть общая переменная, которая определяет, чья очередь. Может быть, что-то вроде этого:
static int whose_turn = 0;
Тогда ваш цикл ожидания может выглядеть так:
pthread_mutex_lock(&mutex);
while (whose_turn != *number) { // check the condition
// it's not my turn yet
pthread_cond_wait(&cond, &mutex);
}
// do my work
output_mandel_line(1, color_val);
// now it's the next thread's turn
whose_turn = (*number + 1) % nthreads;
pthread_mutex_unlock(&mutex);
// wake _all_ the threads, to ensure that the one whose turn it is
// is wakened
pthread_cond_broadcast(&cond);
Дополнительные замечания:
Вам следует свести к минимуму работу, выполняемую в критической области, которую в основном выполняет output_mandel_line()
. Я не уверен, что делает ваша функция set_xterm_color()
, но если можно переместить все вызовы этой функции за пределы критической области, это улучшит вашу производительность. Не в последнюю очередь благодаря тому, что вы можете отправить всю линию одним write()
вызовом.
Для ясности я опустил проверки возвращаемого значения в приведенном выше коде, но вам следует проверять возвращаемое значение каждого вызова функции, которая использует возвращаемое значение для сигнализации об ошибках. За исключением, возможно, случаев, когда вас на самом деле не волнуют ошибки.
Спасибо за решение и все объяснение!!!
У вас состояние гонки. Я думаю, вам нужны две переменные условия. Когда вы делаете
pthread_cond_signal
, удерживая блокировку, и сразу же делаетеpthread_cond_wait
с той же переменной условия, получатель никогда не будет освобожден, потому что отправитель получит сигнал. И вам нужно зациклить доступные условия:pthread_mutex_lock(&mutex); while (line + nthreads < y_chars) pthread_cond_wait(&cond,&mutex); pthread_mutex_unlock(&mutex);
И я не совсем уверен, но вам, возможно, придется освободить мьютекс перед подачей сигнала.