【问题标题】:Linux, C: Accumulate data from multiple threadsLinux,C:从多个线程累积数据
【发布时间】:2015-08-27 20:35:36
【问题描述】:

我有一个扮演网络服务器角色的应用程序,它将 pthread_create 多个线程,每个线程将侦听特定的 TCP 端口并接受多个 TCP 套接字连接。 现在,例如 60 秒后,所有网络客户端(TCP 套接字客户端)都已关闭(但我的应用程序仍在运行这些线程并侦听这些端口),我如何收集数据(例如收到的总字节数)我的应用程序创建的那些线程?

我目前使用的一个解决方案是:在每个socket的accept()中,当有新数据到来时,对应的线程会用pthread_mutex_t更新一个静态变量。但我怀疑这是互斥锁的低效率和浪费时间。

有没有无锁的方法来做到这一点? 如果有任何“per_cpu”计数器的解决方案,就像它在网络驱动程序中使用/没有锁定/互斥锁一样? 或者,当从套接字(read())接收 n 个字节时,我不更新 Receiver_Total_Bytes。相反,我继续计算线程内的总字节数。但问题是,如何从未完成的线程中获取总字节数?

===sudo 代码===

register long Receiver_Total_Bytes = 0;
static pthread_mutex_t Summarizer_Mutex = PTHREAD_MUTEX_INITIALIZER;
void add_server_transfer_bytes(register long bytes )
{
    pthread_mutex_lock( &Summarizer_Mutex );
    Receiver_Total_Bytes += bytes;
    pthread_mutex_unlock( &Summarizer_Mutex );
}

void reset_server_transfer_bytes( )
{
    pthread_mutex_lock( &Summarizer_Mutex );
    Receiver_Total_Bytes = 0;
    pthread_mutex_unlock( &Summarizer_Mutex );
}

然后在socket中读取:

if((n = read(i, buffer, bytes_to_be_read)) > 0) {
    ............
    add_server_transfer_bytes(n);

【问题讨论】:

    标签: c linux multithreading networking


    【解决方案1】:

    另一种选择是为每个线程分配一个结构,并让该结构至少包含所需的计数器,例如 connectionstotal_bytes

    线程本身应该使用原子内置函数来增加这些:

    __sync_fetch_and_add(&(threadstruct->connections), 1);
    __sync_fetch_and_add(&(threadstruct->total_bytes), bytes);
    

    __atomic_fetch_add(&(threadstruct->connections), 1, __ATOMIC_SEQ_CST);
    __atomic_fetch_add(&(threadstruct->total_bytes), bytes, __ATOMIC_SEQ_CST);
    

    这些比非原子操作稍慢,但如果没有争用,开销非常小。 (根据我的经验,缓存线乒乓——当不同的 CPU 尝试同时访问变量时——是一个重大风险,也是导致减速的现实原因,但这里的风险很小。在最坏的情况下,只有当前线程和主线程可以同时访问变量。当然,主线程不应该太频繁地计算摘要,比如每秒一次或两次就足够了。)

    因为结构是在主线程中分配的,所以主线程也可以访问计数器。为了收集总数,它将使用一个循环,并且在循环内部,

    overall_connections += __sync_fetch_and_add(&(threadstruct[thread]->connections), 0);
    overall_total_bytes += __sync_fetch_and_add(&(threadstruct[thread]->total_bytes), 0);
    

    overall_connections += __atomic_load_n(&(threadstruct[thread]->connections));
    overall_total_bytes += __atomic_load_n(&(threadstruct[thread]->total_bytes));
    

    有关__atomic__sync 内置函数的更多信息,请参阅GCC 手册。英特尔 CC 等其他 C 编译器也提供了这些——或者至少曾经提供过;我最后一次验证这是几年前。 __sync 较旧(在较旧的编译器版本中得到更广泛的支持),但 __atomic 反映了 C11 中指定的内存模型,因此未来的 C 编译器更有可能支持。

    【讨论】:

    • 谢谢!还有一件事:如何将计数器重置为 0?我试过这个,但它不起作用: __atomic_store_n( &(streams[t]->bytes), 0, __ATOMIC_RELEASE );
    • @SimonXiao:如果您想阅读并清除它,请使用total_bytes += __atomic_fetch_and(&(streams[t]->bytes), 0, __ATOMIC_SEQ_CST);。我建议使用最严格的要求,只有在你的方案运行后才放宽,并且可以考虑和检查哪些要求是足够的。
    【解决方案2】:

    是的,您的担忧是有道理的。您在这里可以做的最糟糕的事情是按照另一个答案中的建议使用互斥锁。互斥锁抢占线程,因此它们确实是多线程最大的敌人。可能会想到的另一件事是使用原子操作进行递增(在同一个答案中也提到过)。真是可怕的想法!原子操作在争用下表现很差(原子增量实际上是一个循环,它将尝试递增直到成功)。由于在所描述的情况下,我认为竞争会很高,因此原子的行为会很糟糕。

    与原子和互斥体类似的另一个问题是强制内存排序并施加障碍。对性能来说不是一件好事!

    这个问题的真正解决方案当然是让每个线程使用它自己的私有计数器。它不是每个 CPU,而是每个线程。一旦线程完成,这些计数器就可以累积。

    【讨论】:

    • 如果线程使用原子来增加它们的计数器,那么主线程可以不时地累积摘要,比如每秒一次或两次,而不会产生任何可衡量的性能影响。
    • 谢谢。每个线程的计数器听起来不错,但问题是,当该线程未完成/返回时,我如何访问每个线程的计数器?
    • @SimonXiao:这就是我的答案。
    • 是的,@NominalAnimal 对此提供了答案。由于这些计数器是每个线程的,因此原子争用问题不会成为问题。
    • 您假设计数器是一个争论点。这可能是真的,或者根本不是。没有提供这方面的信息。
    猜你喜欢
    • 2018-01-13
    • 2019-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多