хочу сюди!
 

Лия

43 роки, рак, познайомиться з хлопцем у віці 40-50 років

Замітки з міткою «c++»

Атомарные операции Vs Mutex

Сейчас наступило время многоядерных и многопроцессорных систем (даже в последних смартфонах уже 2+ ядер)

Основная проблема многопроцессорного программирование это проблемы одновременного доступа к данным...

Доступ к данным можно блокировать методом mutex-a (spin lock)
pthread_mutex_lock(&mutex);
// некая работа над глобальными данными
pthread_mutex_unlock(&mutex);

А можно при помощи атомарных операций
__sync_add_and_fetch( &global_int, 1 );

Сегодня пришло в голову проверить при каком количестве операций внутри блокируемого блока выгоднее использовать Mutex - а при каком такой-же набор __sync_ операций (т.е. косвенно протестировать сколько стоит pthread_mutex_lock VS __sync)


Фактически тест код mutex
for (i = 0; i < INC_TO; i++)
{
pthread_mutex_lock(&mutex);
for (int j = 0; j < MAX_OPS; j++)
global_int++;
pthread_mutex_unlock(&mutex);
}
И для атомик:

for (i = 0; i < INC_TO; i++)
{

for (int j = 0; j < MAX_OPS; j++)
__sync_add_and_fetch( &global_int, 1 );
}

Результаты тестов:  
при MAX_OPS < 10 -  лучше __sync_add_and_fetch, 
при MAX_OPS >= 10 -  лучше  pthread_mutex_lock  && pthread_mutex_unlock, 

Вывод:  если вам нужно обновить одновременно меньше 10-ти переменных  - при возможности  лучше использовать атомарные операции, иначе - блок pthread_mutex_lock  && pthread_mutex_unlock

Неожиданное поведение функции select

В Linux
man 2 select

НАЗВАНИЕ
       select,   pselect,  FD_CLR,  FD_ISSET,  FD_SET,  FD_ZERO  -  синхронное
       мультиплексирование ввода-вывода

Во FreeBSD - есть еще такая пометка:  "The default size of FD_SETSIZE is currently 1024. In order to accommodate programs which might potentially use a larger number of open files with select(), it is possible to increase this size by having the program define FD_SETSIZE before the inclusion of any header which includes <sys/types.h>." (Что в переводе означает что лимит FD_SET 1024 и чтобы его увеличить нужно переопределить дефайн...

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

struct timeval null_time;
null_time.tv_sec = 2;
null_time.tv_usec = 0;        
fd_set inSet;        
FD_ZERO(&inSet); 
FD_SET(descr, &inSet);  
int res = select(descr+1, &inSet, NULL, NULL, &null_time);

Данный фрагмент кода ожидает когда из сокета можно будет читать данные с таймаутом 2 секунды

И все у нас будет замечательно, пока в один прекрасный момент в нашей программе по какой-то причине не появится 1024+ открытых файлов и значение descr станет больше 1024. И в этот чудесный день функция, как ожидается не вернет -1 (ошибочное значение), а замечательно потрет память за переменной fd_set inSet, что приведет к очень сложно отлаживаемым багам.

Вот пример кода который демонстрирует затирание:
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
        int descr;
        for (int i = 0; i < 1024; i++)
        {
                descr = open("/dev/null", O_RDONLY);
                if (descr < 0)
                {
                        printf("Can't create descr\n");
                        return -1;
                }
        }
        struct timeval null_time;
        null_time.tv_sec  = 2;
null_time.tv_usec = 0;

        fd_set  inSet;
        char pad[] = "1234567890abcdefg";
        printf("pad before select [%s]\n", pad);
        FD_ZERO(&inSet);
        FD_SET(descr, &inSet);
        int res = select(descr+1, &inSet, NULL, NULL, &null_time);
        printf("Pad after select [%s], res=%d\n", pad, res);
}

$ g++ testselect.cpp
$ ./a.out
Pad bafore select [1234567890abcdefg]
Pad after [], res=1

Для избежания затирания я рекомендую использовать poll, для большого числа соединений - epoll. Или в крайнем случае сделать проверку значения descr перед выполнением select


hash_map.insert vs []

Часто в коде можно увидеть использование конструкции

typedef hash_map<Word32, class SomeClass> TSomeHashMap;
TSomeHashMap shm;
shm[10] = SomeClass(val1, val2);


Более оптимальный, в большинстве случаев, следующий код:
shm.insert(TSomeHashMap::value_type(10, SomeClass(val1, val2)));

Лишние затраты в первом случае получаются из-за того что оператор [] это фактически:
(*((shm.insert(TSomeHashMap::value_type(10, SomeClass()))).first)).second = SomeClass(val1, val2);

Т.е. Фактически вначале вызывается пустой конструктор, затем вызывается конструктор создания класса SomeClass с нужными значениями, затем вызывается конструктор копирования и заполняется пустой объект.

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