迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > C语言 >

C 线程资源释放问题

作者:迹忆客 最近更新:2022/12/26 浏览次数:

C语言编程中很大的一个问题就是内存回收和资源释放的问题。在其他多数的高级语言中这些基本上都由底层来自动处理了。但是C语言是需要程序员自己来处理的。

线程的创建也是要占用资源的,当然这些资源是要随着线程的结束然后由操作系统回收的。 所以在多线程编程的过程中,不仅要关注并发和锁的问题,同时也要关心资源释放和回收的问题。 这是在C中避不开的一个事情。

在C中,线程有两种状态:结合态(joinable)分离态(detached)。 这两种状态的线程的资源释放方式是不同的。

  • 结合态: 线程退出后,使用pthread_cancel() 显示的结束线程,资源并不会释放。而是必须显示调用 pthread_join() 来释放资源。
  • 分离态: 线程退出后,自动释放并回收资源,不需要在调用pthread_join() 来回收资源。

严格来说,pthread_join() 并不是用来结束线程的(网上发现有的介绍说该函数是结束并回收资源,这是误导),而是等待线程结束之后来做清理工作的。 也就是说如果主线程创建了一个线程,这个线程一直运行,如果主线程想要终止这个线程,单纯使用pthread_join()是没法终止的,只能在这静静地等着线程结束。必须使用 pthread_cancel() 来终止,然后再使用 pthread_join() 来回收。

在最近做的一个PHP扩展的项目中,就使用到了线程。下面是部分代码

void *start_write_log()
{
    ...
    while(1){
        ...
    }
}

PHP_FUNCTION(jlog_start)
{
    if(server_start != 0) {
        return ;
    }
    server_start = 1;

    if (pthread_mutex_init(&mutex, NULL) != 0){
        // 互斥锁初始化失败
        php_error(E_ERROR,"互斥锁初始化失败\n");
    }

    var_node = PHP_USER_ALLOC(JLOG_VSG(node_size));

    int ret = pthread_create(&tid,NULL,start_write_log,NULL);
}
...
PHP_FUNCTION(jlog_stop)
{
    log_node *n;
    if(server_start == 0) {
        return ;
    }
    server_start = 0;
    while(!checkQueueEmpty() || !idle) {}

    pthread_cancel(tid);     // pthread_cancel() 只是用来结束线程,并不会回收线程的资源
    pthread_join(tid,NULL);  // pthread_join() 用来回收线程,释放其占用的资源。
    PHP_USER_FREE(var_node);
}

可以看到,使用pthread_cancel()结束线程,然后使用pthread_join()来释放资源。 最初的时候并没有考虑到线程资源释放和回收的问题,所以并没有使用pthread_join()。 所以当使用 valgrind 检测的时候出现一个错误:


==24257== 592 bytes in 1 blocks are possibly lost in loss record 65 of 67
==24257==    at 0x4C2C089: calloc (vg_replace_malloc.c:760)
==24257==    by 0x4012734: _dl_allocate_tls (in /usr/lib64/ld-2.17.so)
==24257==    by 0x7C1683B: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.17.so)
==24257==    by 0x8770AE: zend_do_fcall_common_helper_SPEC (zend_vm_execute.h:558)
==24257==    by 0x807BB7: execute_ex (zend_vm_execute.h:363)
==24257==    by 0x7D32CF: zend_execute_scripts (zend.c:1341)
==24257==    by 0x771BC1: php_execute_script (main.c:2613)
==24257==    by 0x878CFB: do_cli (php_cli.c:998)
==24257==    by 0x43572E: main (php_cli.c:1382)

...

==24257== LEAK SUMMARY:
==24257==    definitely lost: 0 bytes in 0 blocks
==24257==    indirectly lost: 0 bytes in 0 blocks
==24257==      possibly lost: 592 bytes in 1 blocks
==24257==    still reachable: 6,973 bytes in 73 blocks
==24257==         suppressed: 0 bytes in 0 blocks
==24257==
==24257== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

这就是说由pthread_create() 申请的资源最后没有释放回收。 加上 pthread_join()之后,问题随之解决了。

pthread_join() 方法除了用于回收资源之外,还有一个功能就是保证由主线程创建的线程一定可以执行完成。 如果没有pthread_join() 那么当主线程执行完成之后,那么创建的线程有可能还没来的及执行整个程序就退出了。 如果程序中没有其他方式的依赖关系来保证线程一定能执行,那么pthread_join() 也是必须的。 也就是说这个方法的作用是 等待线程执行完成然后释放回收资源。所以如果线程没有执行完,那么主线程就会阻塞到调用pthread_join()的地方等待线程执行完。

除了使用 pthread_join() 方法之外,上面我们也说过了,可以将线程创建为分离态。 这样就可以在线程结束后自动释放回收资源了。 体现在项目中的代码如下:

PHP_FUNCTION(jlog_start)
{
    pthread_attr_t attr;
    if(server_start != 0) {
        return ;
    }
    server_start = 1;

    pthread_attr_init(&attr);

    pthread_attr_setdetachstat(&attr,PTHREAD_CREATE_DETACHED);

    if (pthread_mutex_init(&mutex, NULL) != 0){
        // 互斥锁初始化失败
        php_error(E_ERROR,"互斥锁初始化失败\n");
    }

    var_node = PHP_USER_ALLOC(JLOG_VSG(node_size));

    int ret = pthread_create(&tid,NULL,start_write_log,NULL);
    /* 销毁一个目标结构,并且使它在重新初始化之前不能重新使用 */
    
    pthread_attr_destroy (&attr);
}

PHP_FUNCTION(jlog_stop)
{
    log_node *n;
    if(server_start == 0) {
        return ;
    }
    server_start = 0;
    while(!checkQueueEmpty() || !idle) {}

    pthread_cancel(tid);     // pthread_cancel() 只是用来结束线程,并不会回收线程的资源
    PHP_USER_FREE(var_node);
}

上面关于分离态的线程的介绍仅限于使用上,对于其中的数据结构不是这里的重点。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Oracle 的 decode 函数在 MySQL 中的等价物

发布时间:2023/05/09 浏览次数:115 分类:MySQL

本篇文章介绍了三种替代实现,我们可以将它们用作 MySQL 中 Oracle 的 decode() 函数的等价物。 为此,我们将使用 IF()、CASE 以及 FIELD() 和 ELT() 的组合。

更改 MySQL 服务器中的 max_allowed_packet Size

发布时间:2023/05/09 浏览次数:142 分类:MySQL

本篇文章介绍如何更改 MySQL 服务器中的 max_allowed_packet 大小。 为了了解这一点,我们将使用两个操作系统,Windows 10 和 Linux (Ubuntu)。

MySQL ForEach 循环

发布时间:2023/05/08 浏览次数:140 分类:MySQL

本篇文章介绍如何在一条语句中使用 INSERT、SELECT、WHERE 和 JOIN 模拟 MySQL 中的 foreach 循环。

使用 JDBC 连接到 MySQL 数据库

发布时间:2023/05/08 浏览次数:82 分类:MySQL

本文讨论 JDBC 以及使用 JDBC 连接数据库的要求。 我们还查看了一个示例,以了解如何实现 Java 代码来连接 MySQL 数据库。MySQL 为使用 Java 编程语言和 MySQL Connector/J 开发的应用程序提供连接。

遍历 MySQL 表的所有行

发布时间:2023/05/08 浏览次数:161 分类:MySQL

本篇文章介绍了如何使用 WHILE 和 CURSOR 遍历 MySQL 表的所有行。遍历 MySQL 表的所有行

在 C 语言中使用 typedef enum

发布时间:2023/05/07 浏览次数:181 分类:C语言

本文介绍了如何在 C 语言中使用 typedef enum。使用 enum 在 C 语言中定义命名整数常量 enum 关键字定义了一种叫做枚举的特殊类型。

C 语言中的静态变量

发布时间:2023/05/07 浏览次数:151 分类:C语言

本文介绍了如何在 C 语言中使用静态变量。在 C 语言中使用 static 变量在函数调用之间保存变量值

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便