线程参数的高效快捷方式

时间:2011-02-17 17:54:02

标签: c++ c multithreading performance pthreads

使用参数创建线程的最有效方法是什么?参数是一个结构,如果结构不能保留在父线程堆栈上,则有两种解决方案。

动态内存分配

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 个答案:

答案 0 :(得分:2)

在您发布的代码中,最有效的实现是使用堆分配(您的第一个示例)。原因是堆分配(使用new()或malloc)比上下文切换便宜得多。考虑在第二个例子中需要发生什么:

  1. 为Arg分配堆栈空间
  2. 初始化semphore
  3. 启动主题和切换上下文
  4. 将变量复制到新堆栈
  5. 切换上下文返回
  6. 消灭信号量
  7. 分离线程
  8. 切换上下文
  9. 或者,你的第一个例子:

    1. 为Arg分配堆空间
    2. 启动主题
    3. 分离线程
    4. 切换上下文

答案 1 :(得分:0)

这取决于。如果你的结构不大,最好动态分配它以便最小化奇数同步调用。否则,如果您的结构非常大并且您为具有少量内存的系统编写代码,则最好使用信号量(甚至是condvar)。

答案 2 :(得分:0)

原子操作解决方案。这是为你的论点获取记忆的一种非常高速的方式。

如果参数的大小始终相同,则预先分配一堆参数。将pNext添加到结构中以将它们链接在一起。使用pNext创建一个_pRecycle全局,将所有可用的全局链接为链接列表。当你需要一个参数时,在垃圾邮件列表的头部使用原子操作来CAS。完成后,使用原子操作将arg放回垃圾列表的开头。

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点之间换出,则存在竞争条件。如果您的工作需要很长时间来处理,这不会成为问题。否则你需要对列表的头部进行版本化...要做到这一点,你需要能够一次原子地改变两件事...... Unions结合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;

获取记忆:

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只需设置_pRecycle = pRecycle-> pNext,速度只有2倍。 64位和128位CAS与32位一样快。基本上它尖叫。每隔一段时间,当两个线程实际竞争时,并发循环将执行两次。一个人总会赢,所以比赛解决得非常快。