php多进程死锁问题

首先我们来看看什么是死锁
死锁是指两个或多个进程(线程)在执行过程中,由于争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,称这种状态为死锁。
在并发编程中,多个线程在同时执行,它们共享同一些资源,比如锁,内存等。当多个线程同时请求获取某些资源,而这些资源被其他线程占用时,就可能发生死锁。死锁会导致程序无法继续执行,因为不同的线程彼此等待对方释放资源,这样程序就永远不会退出等待状态。
死锁的发生通常由以下几个条件引起:
  • 互斥条件:至少有一种资源是排他性的,即一次只能被一个进程使用。
  • 请求与保持条件:进程已经保持了至少一个资源,但在等待其他资源。
  • 不可剥夺条件:资源不能被抢占,只有在进程完成后才会释放资源。
  • 循环等待条件:多个进程形成一种循环等待对方已经持有的资源。
通过一些实例来说明死锁现象
死锁通常是指多个进程或线程在执行任务时,因争夺系统资源(例如共享内存或共享锁)而导致的一种互相等待的情况,导致所有进程/线程都无法继续执行下去,形成僵局。
以下是一个简单的 PHP 代码示例,展示了死锁的情况:
<?php

// 创建两个共享内存的进程
$pid1 = pcntl_fork();
$pid2 = pcntl_fork();

if ($pid1 == -1 || $pid2 == -1) {
die(‘Failed to fork’);
}

// 共享内存数据
$shm_id = shmop_open(1234, “c”, 0666, 1024);
$shm_data = 0;

// 进程1先获取锁1
if ($pid1 == 0) {
$lock1 = sem_get(1111);
sem_acquire($lock1);

// 进程1尝试获取锁2
$lock2 = sem_get(2222);
sem_acquire($lock2);

// 修改共享内存数据
shmop_write($shm_id, “1”, 0);

// 释放锁2
sem_release($lock2);

// 释放锁1
sem_release($lock1);
exit();
}

// 进程2先获取锁2
if ($pid2 == 0) {
$lock2 = sem_get(2222);
sem_acquire($lock2);

// 进程2尝试获取锁1
$lock1 = sem_get(1111);
sem_acquire($lock1);

// 修改共享内存数据
shmop_write($shm_id, “2”, 0);

// 释放锁1
sem_release($lock1);

// 释放锁2
sem_release($lock2);
exit();
}

// 父进程等待子进程结束
pcntl_waitpid($pid1, $status);
pcntl_waitpid($pid2, $status);

// 输出共享内存数据
$shm_data = shmop_read($shm_id, 0, 1024);
echo “共享内存数据:” . $shm_data . “\n”;

// 关闭共享内存
shmop_close($shm_id);

// 释放锁1和锁2
sem_remove($lock1);
sem_remove($lock2);

以上代码中,创建了两个进程并共享了一个内存块,这两个进程各自尝试获取两个锁,当它们同时获取了自己的锁,但又等待另一个锁的时候,就会形成死锁,因为它们都无法释放自己的锁,导致所有进程都无法继续执行下去,从而产生僵局。
避免死锁的常用技术有以下几种:
1 避免使用多个锁:可以通过优化数据访问方式、缩小锁的范围等方式来减少使用多个锁。
2 避免锁的竞争:可以采用分布式锁等方式避免锁的竞争。
3 避免持有锁的时间过长:可以使用悲观锁或者乐观锁等方式,尽可能缩短持有锁的时间。
4 使用死锁检测机制:当发现死锁时,及时终止其中一个事务,避免死锁的持续发生。
下面是使用乐观锁避免死锁的示例代码:
// 模拟两个用户同时对同一商品进行操作

// 用户A
$productA = Product::find(1);
$productA->quantity = $productA->quantity – 1;
$productA->save();

// 用户B
$productB = Product::find(1);
$productB->quantity = $productB->quantity – 1;
$productB->save();

如果多个用户同时对同一商品进行操作,由于每个用户都是基于自己的版本号进行更新操作,因此不会发生死锁的情况。
高并发遇到死锁了,要如何处理?
当高并发系统中出现死锁时,需要尽快解决以避免系统崩溃。下面是一些处理死锁的常见方法:
1 增加重试次数:当遇到死锁时,可以尝试重新获取资源,等待一段时间后再次执行,直到成功为止。可以使用一个循环来实现重试,例如:
$retryTimes = 3;
while ($retryTimes–) {
try {
// 获取资源
// 执行操作
break;
} catch (DeadlockException $e) {
// 等待一段时间再重试
sleep(1);
continue;
}
}
2 设置超时时间:在获取锁资源时设置超时时间,如果超时则放弃获取锁资源,可以避免死锁的发生。可以使用 Redis 的 SET 命令来实现锁的超时机制,例如:
$lockKey = ‘lock:product:123’;
$lockTimeout = 10; // 锁的超时时间为 10 秒
while (true) {
// 尝试获取锁,如果成功则执行操作
$lockResult = Redis::set($lockKey, 1, ‘EX’, $lockTimeout, ‘NX’);
if ($lockResult === false) {
// 获取锁失败,等待一段时间再重试
sleep(1);
continue;
}
// 获取锁成功,执行操作
// 释放锁
Redis::del($lockKey);
break;
}
3 优化查询语句:在高并发系统中,查询语句的性能是非常重要的。优化查询语句可以减少死锁的发生概率。例如,在执行查询语句时使用索引,减少表连接次数等。
4 分布式锁:在分布式系统中,可以使用分布式锁来避免死锁。分布式锁可以确保多个进程之间对共享资源的访问是有序的,从而避免死锁的发生。可以使用 Redis 的 SETNX 或 NX 参数来实现分布式锁,例如:
$lockKey = ‘lock:product:123’;
$lockTimeout = 10; // 锁的超时时间为 10 秒
while (true) {
// 尝试获取锁,如果成功则执行操作
$lockResult = Redis::set($lockKey, 1, ‘EX’, $lockTimeout, ‘NX’);
if ($lockResult === false) {
// 获取锁失败,等待一段时间再重试
sleep(1);
continue;
}
// 获取锁成功,执行操作
// 释放锁
Redis::del($lockKey);
break;
}
以上是处理死锁的常见方法,可以根据具体情况选择合适的方法来避免死锁的发生。
扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论