PHP curl_multi_remove_handle函数

定义和用法

curl_multi_remove_handle() 函数用于从cURL多句柄中移除一个单独的cURL句柄。移除后,该句柄可以单独关闭或重新用于其他请求。这是cURL多句柄资源管理的关键步骤,防止资源泄漏。

提示: 在请求处理完成后,应该及时从多句柄中移除句柄,然后关闭单个句柄,最后关闭多句柄,这是正确的资源清理流程。

语法

int curl_multi_remove_handle ( resource $mh , resource $ch )

参数

参数 描述 类型 必需
mh curl_multi_init() 返回的cURL多句柄 resource
ch 要移除的cURL句柄,必须已添加到该多句柄中 resource

返回值

成功时返回 0 (CURLM_OK),失败时返回错误代码。

资源清理工作流程

1
curl_multi_info_read()
读取完成信息
2
curl_multi_getcontent()
获取响应内容
3
curl_multi_remove_handle()
从多句柄移除
4
curl_close()
关闭单个句柄
5
curl_multi_close()
关闭多句柄

示例

示例 1:基本用法 - 移除单个句柄

@php
// 创建多句柄
$mh = curl_multi_init();

// 创建并添加多个cURL句柄
$handles = [];
$urls = [
    'https://httpbin.org/get',
    'https://httpbin.org/post',
    'https://httpbin.org/put'
];

foreach ($urls as $i => $url) {
    $handles[$i] = curl_init($url);
    curl_setopt($handles[$i], CURLOPT_RETURNTRANSFER, true);

    // 添加到多句柄
    curl_multi_add_handle($mh, $handles[$i]);
    echo "已添加句柄 {$i} 到多句柄\n";
}

echo "\n当前多句柄中的句柄数量: " . count($handles) . "\n\n";

// 执行并行请求
$active = null;
do {
    curl_multi_exec($mh, $active);
    curl_multi_select($mh, 0.1);
} while ($active > 0);

// 处理完成请求并移除句柄
foreach ($handles as $i => $handle) {
    // 获取内容
    $content = curl_multi_getcontent($handle);
    $info = curl_getinfo($handle);

    echo "请求 {$i} 完成: HTTP {$info['http_code']}, 耗时: " . round($info['total_time'], 3) . "秒\n";

    // 关键步骤:从多句柄中移除句柄
    $removeResult = curl_multi_remove_handle($mh, $handle);

    if ($removeResult === CURLM_OK) {
        echo "✓ 成功从多句柄移除句柄 {$i}\n";
    } else {
        echo "✗ 移除句柄 {$i} 失败,错误代码: {$removeResult}\n";
    }

    // 关闭单个句柄
    curl_close($handle);
    echo "✓ 已关闭句柄 {$i}\n\n";
}

echo "所有句柄已移除并关闭\n";

// 关闭多句柄
curl_multi_close($mh);
echo "多句柄已关闭\n";

echo "\n资源清理完成!\n";
@endphp

示例 2:实时处理与错误安全的移除机制

@php
class SafeMultiHandler {
    private $multiHandle;
    private $activeHandles = [];
    private $completedHandles = [];

    public function __construct() {
        $this->multiHandle = curl_multi_init();
        if ($this->multiHandle === false) {
            throw new Exception('无法初始化cURL多句柄');
        }
    }

    public function addRequest($url, $id = null) {
        if ($id === null) {
            $id = uniqid('req_');
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ]);

        $this->activeHandles[$id] = $ch;
        curl_multi_add_handle($this->multiHandle, $ch);

        return $id;
    }

    public function execute() {
        echo "开始执行并行请求...\n";
        echo "初始句柄数量: " . count($this->activeHandles) . "\n\n";

        $active = null;

        do {
            // 执行多句柄
            $status = curl_multi_exec($this->multiHandle, $active);

            // 实时处理完成请求
            $this->processCompletedRequests();

            // 显示进度
            $completed = count($this->completedHandles);
            $remaining = count($this->activeHandles);
            if ($remaining > 0) {
                echo "进度: {$completed} 完成, {$remaining} 进行中\n";
            }

            // 等待活动
            if ($active > 0) {
                curl_multi_select($this->multiHandle, 0.1);
            }

        } while ($active > 0 || !empty($this->activeHandles));

        echo "\n所有请求执行完成!\n";
        return $this->completedHandles;
    }

    private function processCompletedRequests() {
        $msgsInQueue = null;

        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $handle = $info['handle'];
            $id = array_search($handle, $this->activeHandles, true);

            if ($id !== false) {
                $this->safelyRemoveAndProcess($id, $handle, $info);
            } else {
                echo "警告: 找不到对应的句柄ID\n";
            }
        }
    }

    private function safelyRemoveAndProcess($id, $handle, $info) {
        try {
            // 获取响应内容
            $content = curl_multi_getcontent($handle);
            $curlInfo = curl_getinfo($handle);
            $error = curl_error($handle);

            // 关键步骤:安全地从多句柄移除句柄
            $removeResult = curl_multi_remove_handle($this->multiHandle, $handle);

            if ($removeResult !== CURLM_OK) {
                throw new Exception("移除句柄失败,错误代码: {$removeResult}");
            }

            echo "✓ 安全移除句柄: {$id}\n";

            // 存储结果
            $this->completedHandles[$id] = [
                'success' => ($info['result'] === CURLE_OK),
                'content' => $content,
                'info' => $curlInfo,
                'error' => $error,
                'content_length' => strlen($content)
            ];

            // 关闭单个句柄
            curl_close($handle);
            echo "✓ 已关闭句柄: {$id}\n";

            // 从活动句柄列表中移除
            unset($this->activeHandles[$id]);

        } catch (Exception $e) {
            echo "✗ 处理句柄 {$id} 时出错: " . $e->getMessage() . "\n";

            // 尝试强制清理
            $this->forceCleanupHandle($id, $handle);
        }
    }

    private function forceCleanupHandle($id, $handle) {
        // 尝试从多句柄移除(忽略错误)
        @curl_multi_remove_handle($this->multiHandle, $handle);

        // 尝试关闭句柄
        if (is_resource($handle)) {
            @curl_close($handle);
        }

        // 从活动列表中移除
        unset($this->activeHandles[$id]);

        echo "✓ 已强制清理句柄: {$id}\n";
    }

    public function getRemainingHandles() {
        return $this->activeHandles;
    }

    public function close() {
        echo "\n开始清理剩余资源...\n";

        // 清理所有剩余的活动句柄
        foreach ($this->activeHandles as $id => $handle) {
            echo "清理剩余句柄: {$id}\n";
            $this->forceCleanupHandle($id, $handle);
        }

        // 关闭多句柄
        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
            echo "多句柄已关闭\n";
        }

        $this->activeHandles = [];
        $this->completedHandles = [];

        echo "资源清理完成!\n";
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用安全的处理器
try {
    $handler = new SafeMultiHandler();

    // 添加多个请求
    $requests = [
        'api1' => 'https://httpbin.org/delay/1',
        'api2' => 'https://httpbin.org/delay/2',
        'api3' => 'https://httpbin.org/json',
        'api4' => 'https://httpbin.org/xml'
    ];

    foreach ($requests as $id => $url) {
        $handler->addRequest($url, $id);
    }

    // 执行请求
    $results = $handler->execute();

    // 显示统计信息
    $successCount = count(array_filter($results, function($r) { return $r['success']; }));
    $totalCount = count($results);

    echo "\n执行结果统计:\n";
    echo "总请求数: {$totalCount}\n";
    echo "成功: {$successCount}\n";
    echo "失败: " . ($totalCount - $successCount) . "\n";
    echo "成功率: " . round(($successCount / $totalCount) * 100, 1) . "%\n";

    // 显式关闭(析构函数也会调用)
    $handler->close();

} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}
@endphp

示例 3:句柄重用与资源池管理

@php
class CurlHandlePool {
    private $multiHandle;
    private $handlePool = [];
    private $activeHandles = [];
    private $maxPoolSize;

    public function __construct($maxPoolSize = 10) {
        $this->multiHandle = curl_multi_init();
        $this->maxPoolSize = $maxPoolSize;
    }

    /**
     * 从池中获取或创建句柄
     */
    public function getHandle($url, $options = []) {
        $id = uniqid('req_');

        // 尝试从池中获取空闲句柄
        $ch = $this->getHandleFromPool();

        if ($ch === null) {
            // 池中没有可用句柄,创建新句柄
            $ch = curl_init();
            echo "创建新句柄: {$id}\n";
        } else {
            echo "重用池中句柄: {$id}\n";
        }

        // 重置并配置句柄
        curl_reset($ch);
        $defaultOptions = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ];

        $finalOptions = $options + $defaultOptions;
        curl_setopt_array($ch, $finalOptions);

        // 添加到多句柄
        curl_multi_add_handle($this->multiHandle, $ch);

        $this->activeHandles[$id] = $ch;

        return $id;
    }

    private function getHandleFromPool() {
        if (empty($this->handlePool)) {
            return null;
        }

        // 从池中取出一个句柄
        return array_shift($this->handlePool);
    }

    private function returnHandleToPool($ch) {
        // 检查池大小限制
        if (count($this->handlePool) < $this->maxPoolSize) {
            $this->handlePool[] = $ch;
            echo "句柄已返回池中,当前池大小: " . count($this->handlePool) . "\n";
        } else {
            // 池已满,关闭句柄
            curl_close($ch);
            echo "池已满,关闭句柄\n";
        }
    }

    public function executeBatch() {
        echo "开始执行批处理请求...\n";
        echo "活动句柄数量: " . count($this->activeHandles) . "\n\n";

        $active = null;
        $results = [];

        do {
            // 执行多句柄
            $status = curl_multi_exec($this->multiHandle, $active);

            // 处理完成请求
            $this->processCompletedRequests($results);

            if ($active > 0) {
                curl_multi_select($this->multiHandle, 0.1);
            }

        } while ($active > 0 || !empty($this->activeHandles));

        echo "\n批处理完成!\n";
        return $results;
    }

    private function processCompletedRequests(&$results) {
        $msgsInQueue = null;

        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $handle = $info['handle'];
            $id = array_search($handle, $this->activeHandles, true);

            if ($id !== false) {
                $this->processSingleCompletion($id, $handle, $info, $results);
            }
        }
    }

    private function processSingleCompletion($id, $handle, $info, &$results) {
        // 获取响应内容
        $content = curl_multi_getcontent($handle);
        $curlInfo = curl_getinfo($handle);

        // 关键:从多句柄中移除句柄
        $removeResult = curl_multi_remove_handle($this->multiHandle, $handle);

        if ($removeResult === CURLM_OK) {
            echo "✓ 从多句柄移除句柄: {$id}\n";
        } else {
            echo "✗ 移除句柄失败: {$id}, 错误代码: {$removeResult}\n";
        }

        // 存储结果
        $results[$id] = [
            'success' => ($info['result'] === CURLE_OK),
            'content' => $content,
            'info' => $curlInfo,
            'content_length' => strlen($content)
        ];

        // 从活动句柄中移除
        unset($this->activeHandles[$id]);

        // 将句柄返回池中或关闭
        $this->returnHandleToPool($handle);
    }

    public function getPoolStats() {
        return [
            'pool_size' => count($this->handlePool),
            'active_handles' => count($this->activeHandles),
            'max_pool_size' => $this->maxPoolSize
        ];
    }

    public function close() {
        echo "\n开始关闭资源池...\n";

        // 清理活动句柄
        foreach ($this->activeHandles as $id => $handle) {
            echo "清理活动句柄: {$id}\n";
            curl_multi_remove_handle($this->multiHandle, $handle);
            curl_close($handle);
        }
        $this->activeHandles = [];

        // 清理池中句柄
        foreach ($this->handlePool as $handle) {
            curl_close($handle);
        }
        $this->handlePool = [];

        // 关闭多句柄
        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
        }

        echo "资源池已关闭\n";
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用句柄池
$pool = new CurlHandlePool(5); // 最大池大小5

// 第一轮请求
echo "=== 第一轮请求 ===\n";
$urls1 = [
    'req1' => 'https://httpbin.org/delay/1',
    'req2' => 'https://httpbin.org/delay/1',
    'req3' => 'https://httpbin.org/json'
];

foreach ($urls1 as $id => $url) {
    $pool->getHandle($url, [], $id);
}

$results1 = $pool->executeBatch();
$stats1 = $pool->getPoolStats();
echo "第一轮后池统计: " . json_encode($stats1) . "\n";

// 第二轮请求(应该重用句柄)
echo "\n=== 第二轮请求 ===\n";
$urls2 = [
    'req4' => 'https://httpbin.org/xml',
    'req5' => 'https://httpbin.org/headers',
    'req6' => 'https://httpbin.org/uuid'
];

foreach ($urls2 as $id => $url) {
    $pool->getHandle($url, [], $id);
}

$results2 = $pool->executeBatch();
$stats2 = $pool->getPoolStats();
echo "第二轮后池统计: " . json_encode($stats2) . "\n";

// 显示总结果
$totalRequests = count($results1) + count($results2);
$successfulRequests = count(array_filter($results1, function($r) { return $r['success']; })) +
                     count(array_filter($results2, function($r) { return $r['success']; }));

echo "\n=== 总统计 ===\n";
echo "总请求数: {$totalRequests}\n";
echo "成功请求: {$successfulRequests}\n";
echo "重用节省: " . ($totalRequests - $stats2['pool_size']) . " 个句柄创建\n";

$pool->close();
@endphp

示例 4:高级错误处理与资源监控

@php
class AdvancedResourceManager {
    private $multiHandle;
    private $handles = [];
    private $resourceLog = [];
    private $startTime;

    public function __construct() {
        $this->multiHandle = curl_multi_init();
        $this->startTime = microtime(true);
        $this->logEvent('multi_handle_created', '多句柄已创建');
    }

    public function addRequest($url, $id = null) {
        if ($id === null) {
            $id = uniqid('req_');
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_VERBOSE => false // 设置为true可获取详细日志
        ]);

        $this->handles[$id] = [
            'handle' => $ch,
            'url' => $url,
            'added_time' => microtime(true),
            'state' => 'added'
        ];

        $addResult = curl_multi_add_handle($this->multiHandle, $ch);

        if ($addResult === CURLM_OK) {
            $this->logEvent('handle_added', "句柄 {$id} 已添加到多句柄", $id);
            $this->handles[$id]['state'] = 'active';
        } else {
            $this->logEvent('handle_add_failed', "句柄 {$id} 添加失败,错误代码: {$addResult}", $id);
            curl_close($ch);
            unset($this->handles[$id]);
            return false;
        }

        return $id;
    }

    public function execute() {
        $this->logEvent('execution_started', '开始执行并行请求');

        $active = null;
        $cycleCount = 0;

        do {
            $cycleCount++;

            // 执行多句柄
            $status = curl_multi_exec($this->multiHandle, $active);

            if ($status !== CURLM_OK) {
                $this->logEvent('execution_error', "多句柄执行错误,代码: {$status}");
                $this->handleExecutionError($status);
            }

            // 处理完成请求
            $processed = $this->processCompletedRequests();

            // 资源监控
            if ($cycleCount % 10 === 0) {
                $this->monitorResources();
            }

            // 超时检查
            if ($this->checkTimeout()) {
                $this->logEvent('timeout_reached', '执行超时,强制结束');
                break;
            }

            // 等待活动
            if ($active > 0) {
                $ready = curl_multi_select($this->multiHandle, 0.1);
                if ($ready === -1) {
                    usleep(100000); // 100ms
                }
            }

        } while ($active > 0 || !empty($this->handles));

        $this->logEvent('execution_completed', '所有请求执行完成');

        return $this->generateReport();
    }

    private function processCompletedRequests() {
        $processedCount = 0;
        $msgsInQueue = null;

        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $handle = $info['handle'];
            $id = $this->findHandleId($handle);

            if ($id !== null) {
                $this->processSingleRequest($id, $handle, $info);
                $processedCount++;
            } else {
                $this->logEvent('orphaned_handle', '发现孤儿句柄(找不到对应ID)');
            }
        }

        return $processedCount;
    }

    private function findHandleId($handle) {
        foreach ($this->handles as $id => $data) {
            if ($data['handle'] === $handle) {
                return $id;
            }
        }
        return null;
    }

    private function processSingleRequest($id, $handle, $info) {
        $requestData = $this->handles[$id];

        try {
            // 获取响应内容
            $content = curl_multi_getcontent($handle);
            $curlInfo = curl_getinfo($handle);
            $error = curl_error($handle);

            // 关键步骤:从多句柄移除句柄
            $removeStart = microtime(true);
            $removeResult = curl_multi_remove_handle($this->multiHandle, $handle);
            $removeTime = microtime(true) - $removeStart;

            if ($removeResult === CURLM_OK) {
                $this->logEvent('handle_removed', "句柄 {$id} 已从多句柄移除", $id, [
                    'remove_time' => $removeTime
                ]);

                // 记录请求结果
                $requestData['state'] = 'completed';
                $requestData['completed_time'] = microtime(true);
                $requestData['duration'] = $requestData['completed_time'] - $requestData['added_time'];
                $requestData['result'] = [
                    'success' => ($info['result'] === CURLE_OK),
                    'http_code' => $curlInfo['http_code'],
                    'content_length' => strlen($content),
                    'error' => $error,
                    'remove_result' => $removeResult
                ];

                $this->handles[$id] = $requestData;

            } else {
                $this->logEvent('remove_failed', "句柄 {$id} 移除失败,错误代码: {$removeResult}", $id);
                throw new Exception("移除句柄失败,错误代码: {$removeResult}");
            }

            // 关闭单个句柄
            $closeStart = microtime(true);
            curl_close($handle);
            $closeTime = microtime(true) - $closeStart;

            $this->logEvent('handle_closed', "句柄 {$id} 已关闭", $id, [
                'close_time' => $closeTime
            ]);

            // 从活动列表中移除
            unset($this->handles[$id]);

        } catch (Exception $e) {
            $this->logEvent('processing_error', "处理句柄 {$id} 时出错: " . $e->getMessage(), $id);
            $this->forceCleanup($id, $handle);
        }
    }

    private function forceCleanup($id, $handle) {
        $this->logEvent('force_cleanup', "强制清理句柄 {$id}", $id);

        // 尝试移除句柄(忽略错误)
        @curl_multi_remove_handle($this->multiHandle, $handle);

        // 尝试关闭句柄
        if (is_resource($handle)) {
            @curl_close($handle);
        }

        // 从列表中移除
        unset($this->handles[$id]);
    }

    private function handleExecutionError($errorCode) {
        $errorMessages = [
            CURLM_BAD_HANDLE => '传递了无效的多句柄',
            CURLM_BAD_EASY_HANDLE => '传递了无效的简单句柄',
            CURLM_OUT_OF_MEMORY => '内存不足',
            CURLM_INTERNAL_ERROR => '内部错误'
        ];

        $message = $errorMessages[$errorCode] ?? "未知错误代码: {$errorCode}";
        $this->logEvent('execution_error_detail', "执行错误: {$message}");

        // 根据错误类型决定是否继续执行
        if ($errorCode === CURLM_OUT_OF_MEMORY) {
            $this->emergencyCleanup();
        }
    }

    private function emergencyCleanup() {
        $this->logEvent('emergency_cleanup', '开始紧急清理');

        foreach ($this->handles as $id => $data) {
            $this->forceCleanup($id, $data['handle']);
        }
    }

    private function monitorResources() {
        $memoryUsage = memory_get_usage(true);
        $memoryPeak = memory_get_peak_usage(true);
        $activeHandles = count($this->handles);

        $this->logEvent('resource_monitor', '资源监控', null, [
            'memory_usage_mb' => round($memoryUsage / 1024 / 1024, 2),
            'memory_peak_mb' => round($memoryPeak / 1024 / 1024, 2),
            'active_handles' => $activeHandles,
            'execution_time' => round(microtime(true) - $this->startTime, 2)
        ]);
    }

    private function checkTimeout() {
        $executionTime = microtime(true) - $this->startTime;
        return $executionTime > 60; // 60秒超时
    }

    private function logEvent($type, $message, $handleId = null, $data = []) {
        $event = [
            'timestamp' => microtime(true),
            'type' => $type,
            'message' => $message,
            'handle_id' => $handleId,
            'data' => $data
        ];

        $this->resourceLog[] = $event;

        echo "[" . date('H:i:s') . "] {$message}\n";
        if (!empty($data)) {
            foreach ($data as $key => $value) {
                echo "  {$key}: {$value}\n";
            }
        }
    }

    private function generateReport() {
        $report = [
            'summary' => $this->generateSummary(),
            'resource_log' => $this->resourceLog,
            'performance_metrics' => $this->calculateMetrics()
        ];

        return $report;
    }

    private function generateSummary() {
        $completed = array_filter($this->resourceLog, function($event) {
            return $event['type'] === 'handle_removed';
        });

        $failed = array_filter($this->resourceLog, function($event) {
            return in_array($event['type'], ['remove_failed', 'processing_error']);
        });

        return [
            'total_requests' => count($completed) + count($failed),
            'successfully_processed' => count($completed),
            'failed_processing' => count($failed),
            'total_execution_time' => round(microtime(true) - $this->startTime, 2)
        ];
    }

    private function calculateMetrics() {
        $removeTimes = [];
        $closeTimes = [];

        foreach ($this->resourceLog as $event) {
            if ($event['type'] === 'handle_removed') {
                $removeTimes[] = $event['data']['remove_time'] ?? 0;
            }
            if ($event['type'] === 'handle_closed') {
                $closeTimes[] = $event['data']['close_time'] ?? 0;
            }
        }

        return [
            'avg_remove_time' => !empty($removeTimes) ? round(array_sum($removeTimes) / count($removeTimes), 6) : 0,
            'avg_close_time' => !empty($closeTimes) ? round(array_sum($closeTimes) / count($closeTimes), 6) : 0,
            'total_remove_time' => round(array_sum($removeTimes), 6),
            'total_close_time' => round(array_sum($closeTimes), 6)
        ];
    }

    public function close() {
        $this->logEvent('cleanup_started', '开始资源清理');

        // 清理剩余句柄
        foreach ($this->handles as $id => $data) {
            $this->logEvent('cleaning_handle', "清理剩余句柄 {$id}", $id);
            $this->forceCleanup($id, $data['handle']);
        }

        // 关闭多句柄
        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
            $this->logEvent('multi_handle_closed', '多句柄已关闭');
        }

        $this->logEvent('cleanup_completed', '资源清理完成');

        $this->handles = [];
        $this->resourceLog = [];
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用高级资源管理器
$manager = new AdvancedResourceManager();

// 添加多个请求
$urls = [
    'fast' => 'https://httpbin.org/delay/1',
    'medium' => 'https://httpbin.org/delay/2',
    'slow' => 'https://httpbin.org/delay/3',
    'api1' => 'https://httpbin.org/json',
    'api2' => 'https://httpbin.org/xml'
];

foreach ($urls as $id => $url) {
    $manager->addRequest($url, $id);
}

// 执行请求
$report = $manager->execute();

// 显示报告摘要
$summary = $report['summary'];
echo "\n=== 执行报告摘要 ===\n";
echo "总请求数: {$summary['total_requests']}\n";
echo "成功处理: {$summary['successfully_processed']}\n";
echo "处理失败: {$summary['failed_processing']}\n";
echo "总执行时间: {$summary['total_execution_time']} 秒\n";

// 显示性能指标
$metrics = $report['performance_metrics'];
echo "\n=== 性能指标 ===\n";
echo "平均移除时间: " . ($metrics['avg_remove_time'] * 1000) . " 毫秒\n";
echo "平均关闭时间: " . ($metrics['avg_close_time'] * 1000) . " 毫秒\n";
echo "总移除时间: " . ($metrics['total_remove_time'] * 1000) . " 毫秒\n";
echo "总关闭时间: " . ($metrics['total_close_time'] * 1000) . " 毫秒\n";

$manager->close();
@endphp

为什么需要curl_multi_remove_handle?

资源管理

防止资源泄漏,确保系统资源得到正确释放。

内存优化

及时释放不再需要的网络连接和缓冲区。

性能提升

减少多句柄的负担,提高后续请求的执行效率。

常见错误与正确做法

错误做法 问题 正确做法
忘记移除句柄 导致资源泄漏,多句柄中积累大量无用句柄 在处理完每个请求后立即调用curl_multi_remove_handle()
先关闭句柄再移除 可能产生警告或错误,句柄状态不一致 先移除句柄,再关闭句柄:remove → close
不移除句柄直接关闭多句柄 PHP可能会自动清理,但这不是好习惯,可能在某些情况下出现问题 显式移除所有句柄后再关闭多句柄
忽略返回值 无法知道移除操作是否成功,可能隐藏潜在问题 检查curl_multi_remove_handle()的返回值,处理错误

最佳实践

  • 及时移除 - 在请求处理完成后立即从多句柄中移除句柄
  • 检查返回值 - 始终检查curl_multi_remove_handle()的返回值
  • 正确的清理顺序 - 遵循:获取内容 → 移除句柄 → 关闭句柄 → 关闭多句柄
  • 异常处理 - 在移除失败时实现适当的错误处理机制
  • 资源监控 - 监控内存和句柄使用情况,及时发现资源泄漏
  • 句柄池 - 对于频繁的请求,考虑实现句柄重用机制
  • 强制清理 - 在异常情况下实现安全的强制清理机制

注意事项

重要:
  • curl_multi_remove_handle() 必须在 curl_multi_close() 之前调用
  • 移除句柄后仍然可以调用 curl_multi_getcontent() 获取该句柄的内容
  • 同一个句柄只能从一个多句柄中移除一次
  • 移除不存在的句柄或已移除的句柄会导致错误
  • 在多线程环境中使用时需要特别注意线程安全
  • 即使请求失败,也需要移除句柄并进行清理
  • 大量并发请求时,不及时移除句柄可能导致文件描述符耗尽