Проблема с использованием глобальной переменной - PullRequest
0 голосов
/ 08 мая 2018

Я изучаю Thread, используемый в Linux и операционных системах. Я делал небольшое упражнение. Цель состоит в том, чтобы суммировать значение одной глобальной переменной и в конце посмотреть результат. И когда я посмотрел на конечный результат, мой разум просто взорвался. Код следующий

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

int i = 5;

void *sum(int *info);

void *sum(int *info)
{
    //int *calc = info (what happened?)
    int calc = info;

    i = i + calc;

    return NULL;
}

int main()
{
    int rc = 0,status;
    int x = 5;

    pthread_t thread;

    pthread_t tid;
    pthread_attr_t attr;

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

    rc = pthread_create(&thread, &attr, &sum, (void*)x);
    if (rc) 
    {              
        printf("ERROR; return code from pthread_create() is %d\n", rc);
        exit(-1);
    }

    rc = pthread_join(thread, (void **) &status);
    if (rc) 
    {
        printf("ERROR; return code from pthread_join() is %d\n", rc);
        exit(-1);
    }

    printf("FINAL:\nValue of i = %d\n",i);
    pthread_attr_destroy(&attr);
    pthread_exit(NULL);

    return 0;
}

Если я помещу переменную calc в функцию sum как int * cal, тогда конечное значение i будет 25 (не ожидаемое значение). Но если я поставлю его как int calc, тогда окончательное значение i будет 10 (мое ожидаемое значение в этом упражнении). Я не понимаю, как значение i может быть 25, когда я ставлю переменную calc как int * calc.

Ответы [ 2 ]

0 голосов
/ 09 мая 2018

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

Вы можете получить точно такой же результат, используя следующий код:

int main()
{
    int i = 5;
    int *j = 5;
    i = i + j;
    printf("%d\n", i); // this is 25
}

В данном случае вы присваиваете указатель j значению 5 и «добавляете 5» к этому указателю. Добавление 5 к указателю эквивалентно добавлению достаточного пространства в памяти для хранения 5 объектов, на которые указывает указатель. В этом случае sizeof (int) равно 4, поэтому вы действительно добавляете 4 * 5, что равно 20. Следовательно, результат равен 25, или 5 + 4 * 5 = 25.

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

Позвольте мне привести еще один пример, чтобы прояснить ситуацию:

int main()
{
    int i = 5;
    uint64_t *j = 5;
    i = i + j;
    printf("%d\n", i); // result is 45
}

Поскольку sizeof (uint64_t) равен 8, это эквивалентно добавлению 5 * 8 к исходному значению 5, поэтому результат равен 5 + 5 * 8 = 45.

Этот код демонстрирует много проблем с приведением типов. «x» сначала объявляется как «int», приводится к обобщенному указателю «void *», и неявно приводится к «int *», а затем возвращается к «int». Эти виды кастинга определенно будут стрелять себе в ногу, как вы уже показали здесь.

0 голосов
/ 08 мая 2018

Прочитайте учебник о pthreads . Вы не можете ожидать воспроизводимого поведения при доступе и изменении глобальных данных в нескольких потоках (без дополнительных мер предосторожности при кодировании, связанных с синхронизация ). AFAIU Ваш код демонстрирует некоторые хитрые неопределенное поведение , и вы должны быть напуганы (возможно, это только неопределенное поведение в вашем случае). Чтобы объяснить наблюдаемое конкретное поведение, вам нужно погрузиться в детали реализации (а у вас нет на это времени: изучить сгенерированный ассемблерный код, поведение вашего конкретного оборудования и т. Д.).

Также (поскольку info является указателем на int)

int calc = info;

не имеет большого смысла (я полагаю, вы сделали опечатку). В некоторых системах (например, в моем x86-64 под управлением Linux) указатель шире, чем int (поэтому calc теряет половину битов из info). В других (редких) системах он может быть меньше по размеру. Иногда (i686 под управлением Linux) он может иметь такой же размер. Вам следует рассмотреть intptr_t из <stdint.h>, если вы хотите привести указатели к целым значениям и обратно.

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

Таким образом, вы можете объявить глобальный мьютекс, такой как

 pthread_mutext_t mtx = PTHREAD_MUTEX_INITIALIZER;

(или используйте pthread_mutex_init ), тогда в вашем sum вы наберете

pthread_mutex_lock(&mtx);
i = i + calc;
pthread_mutex_unlock(&mtx);

(см. Также pthread_mutex_lock и pthread_mutex_lock (3p) ). Конечно, вы должны также написать код в вашем main.

Блокировка мьютекса стоит немного дороже (как правило, в несколько десятков раз больше, чем дополнение), даже если он был разблокирован. Вы можете рассмотреть атомарные операции, если вы можете кодировать в C11, так как вы имеете дело с целыми числами. Вы объявите atomic_int i; и будете использовать atomic_load и atomic_fetch_add .

Если вам интересно, см. Также pthreads (7) & futex (7) .

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

Рассмотрите возможность использования средства очистки потока GCC. Опции инструментов и / или Valgrind helgrind

...