Сделать существующий код ANSI C потокобезопасным и повторно входящим

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

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

/******************************************************************/
/*                                                                */
/* CURRENT situaton:                                              */
/* Global variables scattered accross a multitude of source files */
/*                                                                */
/******************************************************************/
static struct myStruct some_struct;
static long instance_counter;
static char[MAX_STR_LEN] instance_name;
static dbConnection * db_conn;
static SharedMemoryPtr * shrmem_ptr;




/*******************************************************************/
/*                                                                 */
/* PROPOSED solution:                                              */
/* Wrap all global variables into one thread local struct and      */
/* provide getter/setter* funcs for the variables (of course, this */
/* means that I will have to modify existing code to use the       */
/* context variable and the getter/setter methods instead of       */
/* directly accessing the global variables)                        */
/*                                                                 */ 
/*******************************************************************/

/* Thread local variables stored in ONE source file */

struct myStruct some_struct;
long instance_counter;
char[MAX_STR_LEN] instance_name;
dbConnection * db_conn;
SharedMemoryPtr * shrmem_ptr;


/* Thread local variable that provides getter/setter funcs to 'globals' */

typedef struct _ctx
{
    /* Getter functions */
    variable1_type (getter_func1*)(void);
    variable2_type (getter_func2*)(void);

    /* Setter functions */
    void (setter_func1*)(variable1_type v1);
    void (setter_func2*)(variable2_type v2);
} Context;

У меня три основных вопроса:

  1. Является ли подход, который я выбрал, хорошим (то есть лучшим или одним из лучших)? Если нет, то есть ли лучший рекомендуемый (то есть "лучшие практики") способ сделать то, что я хочу сделать?

  2. если локальная переменная потока изменяется в одном потоке, отражается ли это изменение в других потоках? Если ответ отрицательный (что, я подозреваю, так и есть), то это означает, что два потока могут работать с разными значениями переменной. Почти во всех приложениях, о которых я могу думать, это неприемлемо - так как существующие многопоточные приложения избегают этого сценария? Мое основное понимание говорит мне, что блокировка должна быть получена перед записью, и когда есть какие-либо потоки, читающие поток записи должен блокировать. Если это не так, то я был бы признателен за разъяснение правильной последовательности событий. Я также был бы признателен за некоторый псевдокод, который показал бы, как реализовать эту операцию чтения / записи, используя мои образцы структур данных.

  3. Псевдокод функций getter, который я написал в структуре контекста, в идеале должен возвращать указатели по очевидным причинам (чтобы избежать копирования потенциально ОГРОМНЫХ структур данных при каждом получении). Однако, согласно странице IBM, на которую я ссылался ранее в своем вопросе (ссылка предоставлена):

Повторно входящая функция не содержит статические данные в последующих вызовах и не возвращает указатель на статические данные. Все данные предоставляются вызывающей функцией.

Поэтому (насколько я понимаю) функции-получатели не могут возвращать указатели на статические данные (если я не ошибаюсь). Может ли кто-нибудь прояснить. Кроме того, если я не должен возвращать указатели из функций gettter, есть ли какой-либо способ / техника, которую я могу использовать, чтобы предотвратить / избежать возврата копий данных (как я уже говорил, некоторые структуры являются ОГРОМНЫМИ / ТЯЖЕЛЫМИ).

[[В сторону]]

Я занимаюсь разработкой Ubuntu 12.04 LTS, поэтому меня интересуют решения, совместимые с POSIX.

1 ответ

Первый вопрос, который вы должны задать для каждой переменной: это они?

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

Профилировщик или отладчик сможет сказать вам, какой у вас тип.

Ваш подход работает нормально, если все переменные относятся к третьему типу, который вы уже упомянули сами, вероятно, не соответствует действительности.

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

Для третьего типа просто поставьте __thread перед ним модификатор и убедитесь, что он инициализирован в параллельном разделе.

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

Для этого есть 3 основные модели:

  • Одиночный замок Каждый доступ должен сначала получить блокировку, использовать переменную, снять блокировку. смотреть на pthread_mutex_* функции для этого. Также вам нужно будет решить, сколько переменных защищает одна блокировка, она может быть настолько мелкой, как одна, вплоть до больших структур или массивов.
  • Блокировка чтения / записи - это лучший выбор, если вы разрешаете множественный одновременный доступ для чтения, но только один доступ на запись за раз. E сть pthread_mutex_* вариант, который делает это.
  • И наконец, атомарность - это особые аппаратные инструкции, способные модифицировать до машинного слова, синхронизированного за один раз. gcc предоставит вам атомные встроенные для этого.

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

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