使用pthread_detach时PHP扩展出现Segmentation Fault问题求助
问题描述
我用C++编写了PHP扩展,已完成编译与安装。但创建线程并调用pthread_detach后,PHP脚本退出时会触发Segmentation Fault;用PHPCPP实现相同逻辑也出现同样错误。我的需求是让PHP脚本停止运行后,扩展中的线程能继续工作,已在PHP7.2和PHP7.4版本测试。
原生C++扩展代码
#include <php.h> #include <pthread.h> #include <utility> #include <string> #include <fstream> #include <iostream> using namespace std; //g++ -shared -o telircoampq.so $(php-config --includes) telircoamq.cpp -fPIC -g $(php-config --libs) //cp telircoampq.so /opt/remi/php74/root/usr/lib64/php/modules/telircoampq.so // php74 -d extension=telircoampq.so davari.php // Function that will be run in the background thread static void* background_function(void* arg) { // Cast the argument back to its original type std::pair<std::string, std::string>* args = static_cast<std::pair<std::string, std::string>*>(arg); // Do something with the arguments... std::string queue = args->first; std::string data = args->second; // Clean up the argument pair delete args; ofstream fout; fout.open("/var/www/c/test/test.mohsen"); // Exit the thread //pthread_exit(NULL); } // PHP function that creates a background thread and passes two arguments to it PHP_FUNCTION(do_my_job) { // Parse the function arguments char* queue; size_t queue_len; char* data; size_t data_len; int result; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &queue, &queue_len, &data, &data_len) == FAILURE) { RETURN_NULL(); } // Create a pair of arguments to pass to the background function std::pair<std::string, std::string>* args = new std::pair<std::string, std::string>(std::string(queue, queue_len), std::string(data, data_len)); // Create a new thread to run the background function pthread_t thread; result =pthread_create(&thread, NULL, background_function, static_cast<void*>(args)); pthread_detach(thread); // Return the queue value to the PHP user //RETURN_STRING(queue); if (result != 0) { // Handle error // ... } // Return the thread ID to the PHP function RETURN_LONG((long)thread); } // Extension initialization function zend_function_entry telircoampq_functions[] = { PHP_FE(do_my_job, NULL) {NULL, NULL, NULL} }; static PHP_MINIT_FUNCTION(telircoampq) { zend_register_functions(NULL, telircoampq_functions, NULL, MODULE_PERSISTENT); return SUCCESS; } // Extension entry point zend_module_entry telircoampq_module_entry = { STANDARD_MODULE_HEADER, "TelircoTest", NULL, PHP_MINIT(telircoampq), NULL, NULL, NULL, NULL, "1.0", STANDARD_MODULE_PROPERTIES }; // Extension initialization function ZEND_GET_MODULE(telircoampq)
PHPCPP实现代码
#include <iostream> #include <phpcpp.h> #include <thread> #include <fstream> #include <string> using namespace std; void TelircoBack(const std::string& queue_name, const std::string& json_str) { std::string file_path = "/var/www/c" + queue_name; std::ofstream file(file_path); if (file.is_open()) { file << json_str; file.close(); } } Php::Value insert_json_to_queue(Php::Parameters& params) { std::string queue_name = params[0]; std::string json_str = params[1]; // Call the TelircoBack function in a separate thread std::thread t(TelircoBack, queue_name, json_str); t.detach(); return queue_name; } extern "C" { PHPCPP_EXPORT void *get_module() { static Php::Extension extension("TelircoAMPQ", "1.0"); // Register the insert_json_to_queue function extension.add<insert_json_to_queue>("insert_json_to_queue", { Php::ByVal("queue_name", Php::Type::String), Php::ByVal("json_str", Php::Type::String) }); return extension.module(); } }
问题原因与解决方案
核心原因
PHP进程退出时会回收整个进程的资源,包括扩展加载的动态链接库(.so文件)。分离线程后,线程仍在后台运行,但PHP进程退出时会卸载扩展的库文件,此时线程如果还在执行库中的代码,就会访问已经被释放的内存,直接触发段错误。另外,PHP的生命周期设计默认不支持扩展线程在主进程退出后继续运行,因为整个进程的地址空间会被操作系统回收。
解决方法
要实现脚本退出后线程继续工作,必须让任务脱离当前PHP进程的地址空间,常见两种可靠方案:
1. 线程转守护进程
在后台线程中调用fork()生成子进程,线程直接退出,子进程作为独立的守护进程执行任务。这样子进程与PHP进程完全分离,PHP退出后不影响子进程运行。
修改原生C++扩展的background_function示例:
static void* background_function(void* arg) { std::pair<std::string, std::string>* args = static_cast<std::pair<std::string, std::string>*>(arg); std::string queue = args->first; std::string data = args->second; delete args; // 生成子进程 pid_t pid = fork(); if (pid == -1) { // fork失败处理 return nullptr; } else if (pid > 0) { // 父线程退出,交给PHP进程回收 pthread_exit(nullptr); } // 将子进程转为守护进程(可选,避免终端关闭影响) setsid(); chdir("/"); umask(0); // 执行后台任务 ofstream fout; fout.open("/var/www/c/test/test.mohsen"); if (fout.is_open()) { fout << data; fout.close(); } // 子进程完成任务后退出 exit(0); }
2. 使用独立任务队列服务
如果需求是异步处理任务,更稳定的方式是将任务发送到独立的消息队列(比如Redis、RabbitMQ),再用一个常驻的独立进程(C++/Go编写)监听队列并处理任务。PHP扩展只负责提交任务,不直接创建后台线程,完全避免进程资源依赖问题。
注意事项
- 绝对不要在扩展的后台线程中调用任何PHP API,Zend引擎不是线程安全的,跨线程调用会导致不可预知的错误。
- 分离线程后,必须确保线程不访问PHP进程的任何资源(比如Zend内存池、PHP变量),否则进程退出时必然触发段错误。
内容的提问来源于stack exchange,提问作者Mohsen Davari
相关产品推荐
相关产品推荐

