чтение pthread из разделяемой памяти

Исходя из CUDA, меня интересует, как разделяемая память читается из потока и сравнивается с требованиями выравнивания чтения CUDA. В качестве примера я буду использовать следующий код:

#include <sys/unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#define THREADS 2

void * threadFun(void * args);

typedef struct {
    float * dataPtr;
    int tIdx,
    dSize;
} t_data;

int main(int argc, char * argv[])
{
    int i,
    sizeData=5;
    void * status;

    float *data;

    t_data * d;

    pthread_t * threads;
    pthread_attr_t attr;

    data=(float *) malloc(sizeof(float) * sizeData );
    threads=(pthread_t *)malloc(sizeof(pthread_t)*THREADS);
    d = (t_data *) malloc (sizeof(t_data)*THREADS);

    data[0]=0.0;
    data[1]=0.1;
    data[2]=0.2;
    data[3]=0.3;
    data[4]=0.4;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (i=0; i<THREADS;i++)
    {
        d[i].tIdx=i;
        d[i].dataPtr=data;
        d[i].dSize=sizeData;
        pthread_create(&threads[i],NULL,threadFun,(void *)(d+i));
    }

    for (i=0; i<THREADS; i++)
    {
        pthread_join(threads[i],&status);
        if(status);
            //Error;
    }
    return 0;
}

void * threadFun(void * args)
{
    int i;

    t_data * d= (t_data *) args;

    float sumVal=0.0;

    for (i=0; i<d->dSize; i++)
        sumVal+=d->dataPtr[i]*(d->tIdx+1);

    printf("Thread %d calculated the value as %-11.11f\n",d->tIdx,sumVal);

    return(NULL);
}

В threadFun весь указатель d указывает на пространство общей памяти (я полагаю). Из того, что я встречал в документации, чтение из нескольких потоков нормально. В CUDA чтения должны быть объединены - есть ли аналогичные ограничения выравнивания в pthreads? т.е. если у меня есть два потока, читающих с одного и того же общего адреса, я предполагаю, что где-то на линии планировщик должен поместить один поток впереди другого. В CUDA это может быть дорогостоящей операцией, и ее следует избегать. Есть ли штраф за «одновременное» чтение из общей памяти — и если да, то настолько ли он мал, что им можно пренебречь? то есть обоим потокам может потребоваться одновременное чтение d->datPtr[0] - я предполагаю, что чтение памяти не может происходить одновременно - это предположение неверно?

Также я прочитал статью от Intel в котором говорится, что при многопоточности используется структура массивов - это согласуется с cuda. Однако, если я это сделаю, мне почти неизбежно понадобится идентификатор потока, который, как я полагаю, потребует от меня использования блокировки мьютекса идентификатора потока до тех пор, пока он не будет прочитан в области потока, это правда или есть какой-то другой способ идентифицировать темы?

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


person Marm0t    schedule 07.06.2011    source источник


Ответы (4)


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

Также снова для вашего идентификатора потока, поскольку вы записываете это значение только из порождающего потока, а затем читаете это значение в фактическом порожденном потоке, нет необходимости в мьютексе или механизме синхронизации... у вас есть только один производитель /consumer для данных. Мьютексы и другие механизмы синхронизации необходимы только в том случае, если есть несколько потоков, которые будут читать и записывать одно и то же местоположение данных.

person Jason    schedule 07.06.2011
comment
но данные, указанные в d с помощью dataPtr, являются общим адресом. В моем примере оба потока читают из общего адреса памяти для вычисления суммы и потенциально могут читать одновременно. Мне интересно, как это происходит - могут ли два потока читать «одновременно» или есть какой-то планировщик, который фактически разделит два чтения? Если два потока могут читать одновременно, как это работает? - person Marm0t; 07.06.2011
comment
По-разному. Во-первых, последовательность операций чтения будет недетерминированной, то есть, если вы не добавите механизм синхронизации, вы не сможете детерминистически определить, какой поток будет читать первым. При этом локальный указатель потока d не указывает на разделяемую память. Указатель d->dataPtr указывает на разделяемую память. В однопроцессорной системе арбитраж на d->dataPtr будет осуществляться через программный планировщик. Однако в многопроцессорной системе арбитраж будет выполняться на уровне аппаратного контроллера памяти. - person Jason; 07.06.2011
comment
Причина этого в том, что в однопроцессорной системе вы в основном делите время выполнения процессора между потоками, а планировщик программного обеспечения обрабатывает, какие потоки выполняются в любой момент времени. Однако многопроцессорная система может фактически выполнять два разных потока одновременно, поэтому, если оба процессора пытаются прочитать одну и ту же ячейку памяти, то этот арбитраж не может быть выполнен на программном уровне, а происходит либо на аппаратном уровне. посредством предварительной выборки данных в локальные кеши, выдачи вызовов когерентности кеша и т. д. - person Jason; 07.06.2011

У процессоров есть кеш. Чтения происходят из кешей, поэтому каждый ЦП/ядро может читать из своего собственного кеша, если соответствующая строка кеша является ОБЩЕЙ. Принудительно записывает строки кэша в состояние EXCLUSIVE, делая недействительными соответствующие строки кэша на других процессорах.

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

person ninjalj    schedule 07.06.2011

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

  2. Я не вижу причин, по которым получение thread_id должно быть синхронизированной операцией. (И вы можете передать свой поток любым значащим для вас идентификатором, это может быть проще, чем получение значимого значения из абстрактного идентификатора)

person AProgrammer    schedule 07.06.2011
comment
Скажем, я использую структуру массивов, идентифицируя свой поток с помощью переменной 'i', определенной в первом цикле for в main. Чтобы идентифицировать поток, я скажу «d.Idx = i» (затем d.dataPtr=(double_ptr) и т. д.). В этом случае я должен использовать мьютекс на d.Idx, так как значение изменится при вызове следующего потока. Моя текущая реализация представляет собой массив структур (что, как сказано в документе Intel, медленнее), хотя все большие элементы структуры указывают на одни и те же массивы. Можно индексировать d[i].stuff (intel говорит медленнее) вместо d.stuff[i] (intel говорит быстрее). - person Marm0t; 07.06.2011
comment
в threadFun d является локальной (т. е. основанной на стеке) переменной, поэтому значение d.Idx будет уникальным для каждого потока без использования мьютекса или чего-либо еще. - person David Winant; 07.06.2011

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

Кроме того, не забывайте, что CUDA — это расчленение C++, а не C, поэтому некоторые вещи могут выглядеть иначе и в этом аспекте. Например, в вашем коде привычка возвращать из malloc обычно не одобряется настоящими программистами C, поскольку она может быть источником незаметных ошибок.

person Jens Gustedt    schedule 07.06.2011