You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:29:53