PHP模拟并发请求的两种实现方式:HTTP连接与cURL_multi
PHP原生环境通常无法直接创建操作系统级的线程,但在Web服务器端,开发者常需实现类似并发的功能以提升效率。本文将解析两种常用的“伪多线程”或并发技术方案:一是基于fsockopen建立独立HTTP连接实现的自定义Thread类,二是利用PHP扩展cURL的multi句柄进行并行数据获取的方法,帮助解决日志写入阻塞或批量图片/数据下载的性能瓶颈问题。
通过fsockopen模拟并发请求
在需要执行耗时任务但不想阻塞当前页面响应的场景下(如后台日志记录),可以利用PHP的 fsockopen 函数向自身脚本发起新的GET请求。这种方式本质上是开启了多个独立的Web请求进程,它们并行执行并随后关闭,从而达到了近似多线程的效果。
第1步:定义并发线程管理类
创建一个名为 thread 的类,用于存储待执行的函数名及其参数,并在初始化时启动并发流程。核心逻辑分为两步:
- 添加任务:使用
addthread()方法将回调函数和对应参数压入队列。 - 执行任务:在
runthread()中,若无特定flag参数,则循环遍历所有任务,利用fsockopen向当前域名和端口发送一个带有索引标识的GET请求;若检测到flag,则只执行对应索引的函数。
class thread {
var $hooks = array();
var $args = array();
function thread() {}
function addthread($func) {
$args = array_slice(func_get_args(), 1);
$this->hooks[] = $func;
$this->args[] = $args;
return true;
}
function runthread() {
// 检查是否指定了要执行的具体线程索引
if(isset($_GET['flag'])) {
$flag = intval($_GET['flag']);
}
if($flag !== null && ($flag || $flag === 0)) {
// 执行指定索引的回调函数
call_user_func_array($this->hooks[$flag], $this->args[$flag]);
} else {
// 并发发起所有请求
for($i = 0, $size = count($this->hooks); $i < $size; $i++) {
$fp = fsockopen($_SERVER['HTTP_HOST'], $_SERVER['SERVER_PORT']);
if($fp) {
// 构造HTTP请求,带上flag参数指向当前循环索引
$out = "GET {$_SERVER['PHP_SELF']}?flag=$i HTTP/1.1\r\n";
$out .= "Host: {$_SERVER['HTTP_HOST']}\r\n";
$out .= "Connection: Close\r\n\r\n";
fputs($fp, $out);
fclose($fp);
}
}
}
}
}
第2步:编写业务回调函数并调用
定义具体的业务逻辑函数(如记录日志),并通过实例化 thread 类添加任务。注意,这些函数将被新的HTTP进程执行,因此应尽量避免依赖全局状态或复杂的上下文切换。
$thread = new thread();
$thread->addthread('action_log', 'a');
$thread->addthread('action_log', 'b');
$thread->addthread('action_log', 'c');
$thread->runthread();
function action_log($info) {
$log = 'log/' . microtime() . '.log';
$txt = $info . "\r\n" . 'Set in ' . Date('h:i:s', time()) . (double)microtime() . "\r\n";
$fp = fopen($log, 'w');
fwrite($fp, $txt);
fclose($fp);
}
提示:上述代码展示了基本的并发写入逻辑。实际生产环境中,考虑到文件IO竞争,建议对 fopen 操作做好互斥锁处理或使用队列服务。
利用cURL_multi实现高效并发抓取
当任务涉及网络请求且需要获取返回结果时(如批量下载图片或API数据),直接使用上面的HTTP连接方式难以收集返回值。此时,PHP内置的 cURL 扩展提供了强大的多句柄处理机制 curl_multi_* 系列函数,这是实现真正意义上高效并发的标准方案。
第1步:封装并发执行类
该类主要包含以下配置与方法:
- 配置项:包括超时时间、是否跟随重定向、是否返回头部等常用选项。
setUrlList():设置待抓取的URL列表数组。execute():核心的并发执行逻辑。它初始化curl_multi_init,将每个URL创建的curl_init句柄加入multi管理器,然后进入循环状态等待所有请求完成。
class curl_multi{
private $url_list=array();
private $curl_setopt=array(
'CURLOPT_RETURNTRANSFER' => 1,
'CURLOPT_HEADER' => 0,
'CURLOPT_NOBODY' => 0,
'CURLOPT_FOLLOWLOCATION' => 0,
'CURLOPT_TIMEOUT' => 6
);
function __construct($seconds=30){
set_time_limit($seconds);
}
public function setUrlList($list=array(){
$this->url_list=$list;
}
public function execute(){
$mh=curl_multi_init();
foreach($this->url_list as $i=>$url){
$conn[$i]=curl_init($url);
foreach($this->curl_setopt as $key => $val){
curl_setopt($conn[$i],preg_replace('/(CURLOPT_w{1,})/ie','$0',$key),$val);
}
curl_multi_add_handle($mh,$conn[$i]);
}
$active=false;
do{
$mrc=curl_multi_exec($mh,$active);
}while($mrc == CURLM_CALL_MULTI_PERFORM);
while($active and $mrc == CURLM_OK){
if(curl_multi_select($mh) != -1){
do{
$mrc=curl_multi_exec($mh,$active);
}while($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
$res=array();
foreach($this->url_list as $i => $url){
$res[$i]=curl_multi_getcontent($conn[$i]);
curl_close($conn[$i]);
curl_multi_remove_handle($mh,$conn[$i]);
}
curl_multi_close($mh);
return $res;
}
}
第2步:执行批量任务并保存结果
实例化后,传入一组图片URL,执行并发下载并将二进制内容保存到本地文件。此方法显著优于串行请求(如 file_get_contents 循环)。
$curl_mul=new curl_multi();
// 这里演示下载同一张图多次以展示并发能力
$url_list = array(
'https://www.jb51.net/img/logo.jpg',
'https://www.jb51.net/img/logo.jpg',
'https://www.jb51.net/img/logo.jpg'
);
$curl_mul->setUrlList($url_list);
$result_data=$curl_mul->execute();
$i=1;
foreach($result_data as $v){
$filename=$i.'.gif';
$fp2=@fopen($filename,'a');
fwrite($fp2,$v);
fclose($fp2);
$i++;
}
需要注意的是,在使用 curl_multi 时,务必确保服务器已加载 PHP cURL 扩展,并且目标主机允许高频并发连接,以免触发防刷机制或被服务器拒绝。以上就是PHP实现类似多线程并发处理的详细内容,更多关于PHP并发编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













