Нужна ли нам блокировка в системе с несколькими считывателями? - PullRequest
0 голосов
/ 24 февраля 2012

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

#define _GNU_SOURCE
#include <sched.h>

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


struct doulnum
{
 int i;
 long int l;
 char c;
 unsigned int ui;
 unsigned long int ul;
 unsigned char uc;
};


long int global_array[100] = {0};


void* start_read(void *_notused)
{
 int i;
 struct doulnum d;
 int di;
 long int dl;
 char dc;
 unsigned char duc;
 unsigned long dul;
 unsigned int dui;
 while(1)
    {
     for(i = 0;i < 100;i ++)
        {
         dl = global_array[i];
         //di = d.i;
         //dl = d.l;
         //dc = d.c;
         //dui = d.ui;
         //duc = d.uc;
         //dul = d.ul;
         if(dl > 5 || dl < 0)
            printf("error\n");
         /*if(di > 5 || di < 0 || dl > 10 || dl < 5)
            {
             printf("i l value %d,%ld\n",di,dl);
             exit(0);
            }
         if(dc > 15 || dc < 10 || dui > 20 || dui < 15)
            {
             printf("c ui value %d,%u\n",dc,dui);
             exit(0);
            }
         if(dul > 25 || dul < 20 || duc > 30 || duc < 25)
            {
             printf("uc ul value %u,%lu\n",duc,dul);
             exit(0);
            }*/
        }
    }
}


int start_write(void)
{
 int i;
 //struct doulnum dl;
 while(1)
    {
     for(i = 0;i < 100;i ++)
        {
         //dl.i = random() % 5;
         //dl.l = random() % 5 + 5;
         //dl.c = random() % 5 + 10;
         //dl.ui = random() % 5 + 15;
         //dl.ul = random() % 5 + 20;
         //dl.uc = random() % 5 + 25;
         global_array[i] = random() % 5;
        }
    }
 return 0;
}


int main(int argc,char **argv)
{
 int i;
 cpu_set_t cpuinfo;
 pthread_t pt[3];
 //struct doulnum dl;
 //dl.i = 2;
 //dl.l = 7;
 //dl.c = 12;
 //dl.ui = 17;
 //dl.ul = 22;
 //dl.uc = 27;
 for(i = 0;i < 100;i ++)
    global_array[i] = 2;
 for(i = 0;i < 3;i ++)
     if(pthread_create(pt + i,NULL,start_read,NULL) < 0)
        return -1;
/* for(i = 0;i < 3;i ++)
        {
         CPU_ZERO(&cpuinfo);
         CPU_SET_S(i,sizeof(cpuinfo),&cpuinfo);
         if(0 != pthread_setaffinity_np(pt[i],sizeof(cpu_set_t),&cpuinfo))
                {
                 printf("set affinity %d\n",i);
                 exit(0);
                }
        }
 CPU_ZERO(&cpuinfo);
 CPU_SET_S(3,sizeof(cpuinfo),&cpuinfo);
 if(0 != pthread_setaffinity_np(pthread_self(),sizeof(cpu_set_t),&cpuinfo))
        {
         printf("set affinity recver\n");
         exit(0);
        }*/
 start_write();
 return 0;
}

Ответы [ 5 ]

1 голос
/ 24 февраля 2012

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

0 голосов
/ 24 февраля 2012

Это зависит от того, насколько вы заботитесь о переносимости.

По крайней мере, на реальном процессоре Intel x86, когда вы читаете / пишете двойные (32-битные) данные, которые также выровнены, это дает аппаратное обеспечение.Вы атомарны «бесплатно», т. е. без необходимости какой-либо блокировки для ее принудительного применения.

Изменение многих параметров (вплоть до флагов компилятора, которые могут повлиять на выравнивание data'а) можетсломайте это - но способами, которые могут оставаться скрытыми в течение долгого времени (особенно если у вас низкий уровень конкуренции по конкретному элементу данных).Это также приводит к чрезвычайно хрупкому коду - например, переключение на более мелкий тип данных может нарушить код, даже если вы используете только подмножество значений.

Текущая атомарная «гарантия» довольнослучайный побочный эффект от способа разработки кеша и шины.Хотя я не уверен, что действительно ожидал бы изменений, которые сломали бы вещи, я бы тоже не стал считать их надуманными.Единственное место, где я видел документацию об этом атомарном поведении, было в тех же руководствах по процессорам, которые охватывают такие вещи, как регистры для конкретной модели, которые определенно изменились (и продолжают изменяться) от одной модели процессора к другой.

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

0 голосов
/ 24 февраля 2012

Общий ответ: вам нужен какой-то способ обеспечить / обеспечить необходимую атомарность, чтобы читатель не увидел противоречивое состояние.

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

Это касается как архитектуры целевого хоста, так и, в некоторой степени, компилятора.

В вашем примере вы записываете long в массив. В этом случае вопрос заключается в хранении длинного атома? Возможно, но это зависит от хоста. Возможно, что ЦП записывает часть длинных (верхние / нижние слова / байты) отдельно, и, таким образом, читатель может получить значение, которое никогда не записывается. (Я полагаю, что это маловероятно для большинства современных архитектур ЦП, но вам следует проверить это.)

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

Наконец, вам, вероятно, нужно пометить массив как volatile (опять же, я давно этого не делал, так что разбираюсь в особенностях), чтобы компилятор не делал предположения о том, что данные не меняются под ним.

0 голосов
/ 24 февраля 2012

Вам обязательно нужна синхронизация здесь.Простая причина в том, что существует явная вероятность того, что данные будут в несогласованном состоянии, когда start_write обновляет информацию в глобальном массиве, и один из ваших трех потоков пытается прочитать те же данные из глобального массива.То, что вы цитируете, также неверно.«должен быть блокировкой для защиты данных от одновременного доступа читателя и писателя» должен быть «должен быть блокировкой для защиты данных от изменения читателем и писателем одновременно»

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

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

0 голосов
/ 24 февраля 2012

Будет нормально работать, если потоки только читают из global_array.printf должно быть в порядке, поскольку в режиме добавления выполняется одна операция ввода-вывода.

Однако, поскольку основной поток вызывает start_write, чтобы обновить global_array, в то время как другие потоки находятся в start_read тогда они будут читать значения очень непредсказуемым образом.Это в значительной степени зависит от того, как реализованы потоки в ОС, сколько у вас процессоров / ядер и т. Д. Это может хорошо работать на вашем двухъядерном процессоре разработки, но может неэффективно работать при переходе на 16-ядерный рабочий сервер.*

Например, если потоки не синхронизировались, они могут никогда не увидеть никаких обновлений global_array при правильных обстоятельствах.Или некоторые темы увидят изменения быстрее, чем другие.Все дело в том, когда страницы памяти сбрасываются в центральную память и когда потоки видят изменения в своих кэшах.Для обеспечения согласованных результатов необходима синхронизация (барьеры памяти) для принудительного обновления кэшей.

...