C 线程资源释放问题
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);
}
上面关于分离态的线程的介绍仅限于使用上,对于其中的数据结构不是这里的重点。
相关文章
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 修复 Data Is Truncated for a Column 错误
发布时间:2023/05/09 浏览次数:72 分类:MySQL
-
本文介绍 MySQL 错误 Data is truncated for a column 的可能原因和解决方法。修复数据因 MySQL 中的列错误而被截断
MySQL 错误 Error Server PID File Could Not Be Found 解决
发布时间:2023/05/09 浏览次数:98 分类:MySQL
-
在这篇文章中,我们将研究错误! Error Server PID File Could Not Be Found! 在 MySQL 及其解决方案中有充分的解释。
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 关键字定义了一种叫做枚举的特殊类型。