Эффективный и быстрый способ аргументации потока - PullRequest
3 голосов
/ 17 февраля 2011

Каков наиболее эффективный способ создания потока с аргументом?Аргументом является структура, если структура не может оставаться в стеке родительского потока, есть два решения.

С динамическим выделением памяти

struct Arg{
    int x;
    int y;
};

void* my_thread(void* v_arg){
    Arg* arg = (Arg*) v_arg;

    //... running

    delete arg;
    return NULL;
}

//Creating a thread
void a_function(){
    Arg* arg = new Arg;
    arg->x = 1; arg->y = 2;

    pthread_t t;
    pthread_create(&t, NULL, my_thread, arg);
    pthread_detach(t);
}

С семафором

struct Arg{
    sem_t sem;
    int x;
    int y;
};

void* my_thread(void* v_arg){
    Arg* arg = (Arg*) v_arg;
    int arg_x = v_arg->x;
    int arg_y = v_arg->y;
    sem_post( &(v_arg->sem) );

    //... running

    return NULL;
}

//Creating a thread
void a_function(){
    Arg arg;
    arg.x = 1; arg.y = 2;
    sem_init( &(arg.sem), 0, 0);

    pthread_t t;
    pthread_create(&t, NULL, my_thread, &arg);
    pthread_detach(t);

    sem_wait( &(arg.sem) );
    sem_destroy( &(arg.sem) );
}

Я работаю с Linux и Windows.

Ответы [ 3 ]

2 голосов
/ 17 февраля 2011

В размещенном вами коде наиболее эффективной реализацией является использование выделения кучи (ваш первый пример).Причина этого заключается в том, что выделение кучи (с использованием new () или malloc) намного дешевле, чем переключение контекста.Рассмотрим, что должно произойти во втором примере:

  1. Выделение стекового пространства для Arg
  2. Инициализация семфора
  3. Запуск потока и переключение контекста
  4. Скопировать переменные в новый стек
  5. Переключить контекст назад
  6. Уничтожить семафор
  7. Отсоединить поток
  8. Переключить контекст

В качестве альтернативы, ваш первый пример:

  1. Выделить пространство кучи для Arg
  2. Запустить поток
  3. Отсоединение потока
  4. Переключение контекста
0 голосов
/ 07 апреля 2011

Атомное решение. Это очень быстрый способ получить память для ваших аргументов.

Если аргументы всегда имеют одинаковый размер, предварительно выделите их несколько. Добавьте pNext в вашу структуру, чтобы связать их вместе. Создайте глобальный _pRecycle для хранения всех доступных в виде связанного списка, используя pNext для их связывания. Когда вам нужен аргумент, используйте атомарные операции для CAS один из заголовка списка мусора. Когда вы закончите, используйте атомарные операции, чтобы вернуть аргумент в начало списка мусора.

CAS относится к чему-то вроде __ sync_bool_compare_and_swap , который возвращает 1 в случае успеха.

чтобы захватить память аргументов:

while (1)  {  // concurrency loop
  pArg = _pRecycle;  // _pRecycle is the global ptr to the head of the available arguments
  // POINT A
  if (CAS(&_pRecycle, pArg->pNext, pArg))  // change pRecycle to next item if pRecycle hasn't changed.
    break; // success
  // POINT B
}
// you can now use pArg to pass arguments

чтобы перезапустить память аргументов после завершения:

while (1)  {  // concurrency loop
  pArg->pNext = _pRecycle;
  if (CAS(&_pRecycle, pArg, pArg->pNext))  // change _pRecycle to pArg if _pRecycle hasn't changed.
    break; // success
}
// you have given the mem back 

Существует состояние гонки, если что-то использует и перезапускает pArg, в то время как другой поток заменяется между точками A и B. Если ваша работа занимает много времени, это не будет проблемой. В противном случае вам нужно создать версию заголовка списка ... Для этого вам нужно иметь возможность атомарно изменять две вещи одновременно ... Объединения в сочетании с 64-битным CAS на помощь!

typedef union _RecycleList {
  struct {
    int   iversion;
    TArg *pHead;
  }
  unsigned long n64;  // this value is iVersion and pHead at the same time!
} TRecycleList;

TRecycleList _Recycle;

, чтобы получить mem:

while (1)  // concurrency loop
{
  TRecycleList Old.n64 = _Recycle.n64;
  TRecycleList New.n64 = Old.n64;
  New.iVersion++;
  pArg = New.pHead;
  New.pHead = New.pHead->pNext;
  if (CAS(&_Recycle.n64, New.n64, Old.n64)) // if version isnt changed we get mem
    break; // success
}

чтобы вернуть mem:

while (1)  // concurrency loop
{
  TRecycleList Old.n64 = _Recycle.n64;
  TRecycleList New.n64 = Old.n64;
  New.iVersion++;
  pArg->pNext = New.pHead;
  New.pHead = pArg;
  if (CAS(&_Recycle.n64, New.n64, Old.n64))  // if version isnt changed we release mem
    break; // success
}

Поскольку в 99,9999999% случаев нет двух потоков, выполняющих код для захвата памяти одновременно, вы получаете отличную производительность. Наши тесты показали, что CAS всего в 2 раза медленнее, чем просто установка _pRecycle = pRecycle-> pNext. 64-битный и 128-битный CAS так же быстр, как 32-битный. В основном это крики. Каждый раз через некоторое время цикл параллелизма будет выполняться дважды, когда два потока на самом деле участвуют в гонке. Один всегда победит, поэтому гонка проходит очень быстро.

0 голосов
/ 17 февраля 2011

Это зависит.Если ваша структура не большая, лучше распределить ее динамически, чтобы минимизировать нечетные вызовы синхронизации.В противном случае, если ваша структура довольно большая и вы пишете код для системы с небольшим объемом памяти, лучше использовать семафор (или даже condvar).

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