Мьютекс для динамически выделяемой памяти в C

Я читаю Thread Synchronization из книги «Расширенное программирование в среде unix».

В этом разделе приведен пример использования мьютекса с динамически размещаемый объект. У меня есть некоторые сомнения в том же.

Здесь я делюсь хронологией событий (сверху вниз), чтобы объяснить свои сомнения:

  1. Thread1 создан.
  2. Thread1 создает мьютекс-переменную, инициализирует его и помещает в глобальный список, чтобы другие могли его использовать.
  3. Теперь Thread1 получил блокировку для использования совместно используемой структуры данных, скажем, ds. Thread1 нужно проделать очень большой объем работы с ds, т.е. Thread1 собирается получить эту блокировку в течение длительного времени.
  4. Теперь, когда Thread1 все еще получил блокировку, создается Thread2.
  5. Теперь Thread2 тоже хочет использовать ds.
  6. Таким образом, Thread2 должен сначала увеличить счетчик, показывающий, что ссылка на ds увеличена. Для этого (согласно книге) сначала необходимо получить блокировку, используя ту же переменную mutex_t, прежде чем увеличивать счетчик.
  7. Но поскольку Thread1 уже получил блокировку для этой переменной mutex_t, поэтому, когда Thread2 вызывает lock() перед увеличением счетчика, ему придется подождать, пока Thread1 не разблокирует блокировку.

Сомнения:

  1. О каком глобальном списке он говорит (означает просто создать какой-либо список и передать ссылку на него всем потокам или любому конкретному списку)?
  2. Когда Thread1 создал переменную блокировки, он установил счетчик на 1. Затем Thread2 ожидает увеличения этого счетчика до 2. Но предположим ситуацию, в которой после выполнения текущей работы Thread1 не нужно использовать ds. Поэтому перед разблокировкой также уменьшите счетчик или сначала разблокируйте его, а затем вызовите foo_rele(), чтобы снова заблокировать и уменьшить счетчик. Теперь возможно, что до того, как Thread2 увеличит счетчик, Thread1 уменьшит его. Если да (по моему мнению), то моя структура данных будет уничтожена? Так что я думаю, что в этом примере книги есть небольшая ошибка. Было бы лучше, если бы мы использовали разные mutex_var для увеличения счетчика?

person Abhishek Gupta    schedule 29.12.2012    source источник
comment
Объясните, о каком примере вы говорите? На странице, которую вы сказали, есть как минимум 5 различных примеров ..   -  person Marco    schedule 29.12.2012
comment
Рисунок 11.10. Использование мьютекса для защиты структуры данных   -  person Abhishek Gupta    schedule 29.12.2012


Ответы (3)


А. Я думаю, что под термином «глобальный список» автор понимает все переменные, которые разделяются между потоками.

Пример:

struct foo* shared_foo; /* This pointer is shared between all threads */

struct foo* foo_alloc(void)
{
   /* This pointer is local to the thread which allocates the memory */
   struct foo *fp;

    if ((fp = malloc(sizeof(struct foo))) != NULL) {
        /* whatever */
    }
    /* local pointer value returned */
    return(fp);
}

/* probably somewhere in the code the shared pointer (on the 'global list') is initialized this way */
shared_foo = foo_alloc();

Б. Хм ... Я действительно не понимаю, что вы говорите. Не могли бы вы написать свой сценарий в виде списка? На мой взгляд, f_count устанавливается во время инициализации как флаг «Этот мьютекс используется». Поэтому, когда мьютекс свободен, значение f_count устанавливается в 1. Когда Thread1 получает блокировку, его значение устанавливается в 2. Когда он освобождает блокировку, значение возвращается в 1. Допустимые значения f_count: 1 (инициализировано и свободно. ) и 2 (инициализировано и занято). Чтобы освободить мьютекс, вам просто нужно дважды вызвать foo_rele, когда он занят (f_count = 2), или один раз, когда он свободен (f_count = 1). Затем значение f_count достигает 0, и мьютекс удаляется.

person Adam Sznajder    schedule 29.12.2012

Итак, пример 11.10 посвящен содержимому ВНУТРИ структуры foo. Каждая структура имеет блокировку, поэтому для того, чтобы поток 1 работал с объектом, потоку 1 необходимо удерживать мьютекс внутри объекта.

Приведенный пример неполон, и я могу понять ваше замешательство. foo_rele не должен вызываться, пока поток больше не хочет этого объекта. Если другой поток хочет использовать foo, он должен вызвать foo_hold () для увеличения счетчика ссылок (fp-> count ++). И да, существует состояние гонки, при котором поток 2 может ХОТИТЬ получить его, а поток 1 освобождает его.

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

Надеюсь, это поможет.

person Mats Petersson    schedule 29.12.2012

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

  2. При использовании мьютексов вы должны рассчитывать на то, что потоки могут блокировать мьютекс в любом порядке (POSIX не гарантирует какой-либо конкретный порядок). Таким образом, вполне возможно, что поток 1 создает, использует и уничтожает структуру до того, как какой-либо другой поток получит доступ к мьютексу.

P.S. Я понимаю твои сомнения. Во фрагменте кода мне действительно не хватает другого мьютекса, фактически предотвращающего одновременную запись в структуру.

person davak    schedule 29.12.2012