Как использовать pthreads для одновременного выполнения операций с массивом с ограничением?

Я использую pthreads в C, чтобы выполнить две операции с массивом int: одна операция удваивает значение ячейки, другая операция уменьшает вдвое значение ячейки. Если после удвоения ячейки ее значение станет больше максимально допустимого значения, потоку необходимо дождаться, пока другой поток не уменьшит вдвое значение этой ячейки. Я инициализировал массив следующим образом: первые 5 ячеек имеют значение, очень близкое к разрешенному максимуму, а остальные пять имеют значение далеко от максимума.

Я решил использовать для этого глобальный мьютекс и условную переменную. В main сначала создается 10 удвоенных потоков, а затем еще 10 половинных потоков. Но потом моя программа зависает. Я не могу понять, в чем проблема, любая помощь приветствуется.

Моя мотивация - лучше понять потоки pthread и условные переменные.

Это код:

#include <stdio.h>
#include <stdlib.h>
#include <ntsid.h>
#include <pthread.h>
#include <unistd.h>

#define MAX 20
#define THREADS_NUM 10
#define OFFSET 10

typedef struct myStruct {
    int cellId;
} myStruct;

int * cells;

pthread_mutex_t globalMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t globalCond = PTHREAD_COND_INITIALIZER;

pthread_t threads[THREADS_NUM * 2];

void * DoublerThread(void * arg) {
    myStruct * myStr = (myStruct *) arg;
    int id = myStr->cellId;
    pthread_mutex_t mutex = globalMutex;
    pthread_cond_t condition = globalCond;

    pthread_mutex_lock(&mutex);
    while((cells[id] * 2) > MAX) {
        printf("Waiting... id = %d\n", id);
        pthread_cond_wait(&condition, &mutex);
    }
    cells[id] *= 2;
    printf("new val = %d, id = %d\n", cells[id], id);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

void * HalverThread(void * arg) {
    myStruct * myStr = (myStruct *) arg;
    int id = myStr->cellId;
    pthread_mutex_t mutex = globalMutex;
    pthread_cond_t condition = globalCond;
    sleep(1);
    pthread_mutex_lock(&mutex);
    cells[id] /= 2;
    pthread_cond_broadcast(&condition);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

void initMyStructs(myStruct ** myStructs) {
    int i;
    for(i = 0; i < THREADS_NUM * 2; i++) {
        myStructs[i] = (myStruct *) malloc(sizeof(myStruct) * 2);
        if (!myStructs[i]) {
            printf("malloc error\n");
            exit(EXIT_FAILURE);
        }
        myStructs[i]->cellId = i % THREADS_NUM;
    }
}

void initCells() {
    int i, tmp;
    cells =(int *) malloc(sizeof(int));
    if (!cells) {
        printf("malloc error\n");
        exit(EXIT_FAILURE);
    }
    for(i = 0; i <= THREADS_NUM; i++) {
        if (i < THREADS_NUM / 2) {
            cells[i] = MAX - 1;
        } else {
            tmp = cells[i] = 1;
        }
    }
}

int main() {
    int i;
    myStruct ** myStructs;
    initMyStructs(myStructs);
    initCells();

    //create 10 Doubler threads
    for(i = 0; i < THREADS_NUM; i++) {
        pthread_create(&threads[i], NULL, DoublerThread, (void *) myStructs[i]);
    }
    //create 10 Halver threads
    for(i = 0; i < THREADS_NUM; i++) {
        pthread_create(&threads[i + OFFSET], NULL, HalverThread, (void *) myStructs[i + OFFSET]);
    }
    for(i = 0; i < THREADS_NUM + OFFSET; i++) {
        pthread_join(threads[i], NULL);
    }
    return 0;
}

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

Yunnosch 03.05.2018 21:43

@Yunnosch Я добавил мотивировку проблемы, не уверен, что вы имеете в виду "array init", у меня есть функция initCells.

Yos 03.05.2018 21:49

Приведите пример значений массивов и то, как они развиваются с течением времени под влиянием описываемых вами потоков.

Yunnosch 03.05.2018 21:52

@Yunnosch в моем коде есть пример возможного массива, массива cells. Если вы посмотрите на код, вы увидите, что его значения первой половины равны 19, а вторая половина - 1 (также описано в OP). есть операторы printf, которые печатают новые значения некоторых ячеек, но вы не можете увидеть много разных значений, потому что программа очень быстро зависает

Yos 03.05.2018 21:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
197
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы создали «частные» мьютексы и условные переменные для каждого потока, поэтому они не синхронизируются каким-либо (значимым) способом. Вместо этого:

pthread_mutex_t mutex = globalMutex;
pthread_cond_t condition = globalCond;

Просто используйте globalMutex и globalCond - это то, что вам действительно нужно.

[ Я переместил это сюда, потому что думаю, что мы должны это сделать. Я не могу интуитивно понять ТАК-Икетт. ]

By the way, just to make sure I understand this, the mutex is per cell, so that multiple threads can work on multiple cells simultaneously, right? Just not two threads on the same cell. –

Итак, то, что вы, вероятно, хотите, это что-то вроде:

typedef struct myStruct {
    int cellId;
    pthread_mutex_t lock;
    pthread_cond_t  wait;
} myStruct;

и в InitMyStruct ():

myStructs[i]->cellId = i % THREADS_NUM;
pthread_mutex_init(&myStructs[i]->lock, NULL);
pthread_cond_init(&myStructs[i]->wait, NULL);

и в Halvers:

pthread_mutex_lock(&myStr->lock);
cells[id] /= 2;
pthread_cond_broadcast(&myStr->wait);
pthread_mutex_unlock(&myStr->lock);

и дублер: ...

   pthread_mutex_lock(&myStr->lock);
    while((cells[id] * 2) > MAX) {
        printf("Waiting... id = %d\n", id);
        pthread_cond_wait(&myStr->wait, &myStr->lock);
    }
    cells[id] *= 2;
    printf("new val = %d, id = %d\n", cells[id], id);
    pthread_mutex_unlock(&myStr->lock);

So currently, only one thread can make changes to the array at a time? But then the program exits after about a second, if threads couldn't be making changes to the array simultaneously then wouldn't the program take 10 seconds to finish, because each HalverThread sleeps for 1 second. – Yos 6 hours

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

Потрясающе, спасибо! Я был смущен тем, что локальная переменная pthread_mutex_t «указывает» на глобальную (потому что это структура), но теперь я понимаю, что для этого мне пришлось бы использовать явный указатель. Смущает Ява.

Yos 03.05.2018 22:06

«Ява смущает» - разве мы не все :)

mevets 03.05.2018 22:09

Кстати, чтобы убедиться, что я это понимаю, мьютекс для каждой ячейки, так что несколько потоков могут работать с несколькими ячейками одновременно, верно? Только не два потока в одной ячейке.

Yos 03.05.2018 22:47

Для этого вам понадобится либо массив из мьютексов + condvar, либо какой-либо другой способ связать их с ячейкой. Важно то, что единственный определенный способ гарантировать, что оба потока используют один и тот же мьютекс, - это использовать один и тот же адрес для структуры мьютекса. Копии могут работать в некоторых реализациях, но не во всех.

mevets 04.05.2018 00:56

Итак, в настоящее время только один поток может вносить изменения в массив за раз? Но затем программа завершается примерно через секунду, если потоки не могут одновременно вносить изменения в массив, тогда программа не займет 10 секунд, потому что каждый HalverThread спит 1 секунду.

Yos 04.05.2018 08:05

Другие вопросы по теме