记一次swoole下协程文件死锁
bug原因:
# 协程中获取拼音首字母,要获取字典文件内容,fseek 方法对同一个文件指针中定位
fseek($fp, $offset * 8, SEEK_SET);
解决方式
/**
* 获取$string的拼音
* @param string $string 词条字符串
* @param bool $first 是否只取首字母
* @return string
*/
public static function getPY($string, $first = false, $phonetic = false)
{
$count = 10;
$key = __CLASS__ . __FUNCTION__;
// 协程使用该方法会死锁,这里加一个1s的redis锁
start:
$redis = RedisUtil::redis();
if ($redis->incr($key) !== 1) {
$redis->expire($key, 1);
var_dump(\Hyperf\Utils\Coroutine::id() . ":getPY 等待锁~");
$count--;
if ($count < 0) {
return '';
}
// 等待 20ms - 40 ms
usleep(rand(20000, 40000));
goto start;
} else {
// var_dump("getPY 运行成功");
$redis->expire($key, 1);
$pdat = BASE_PATH . '/storage/py.dat'; // 多音字dat数据
$fp = fopen($pdat, 'rb');
if (!$fp) {
return '*';
}
$in_code = strtoupper('utf-8');
$out_code = 'GBK';
$strlen = mb_strlen($string, $in_code);
$ret = '';
for ($i = 0; $i < $strlen; $i++) {
$py = '';
$izh = mb_substr($string, $i, 1, $in_code);
if (preg_match('/^[a-zA-Z0-9]$/', $izh)) {
if ($first && $i == 0) {
$ret = $izh;
break;
}
$ret .= $izh;
} elseif (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $izh)) { // 只取纯汉字,其他非汉字符号一概忽略。
$char = mb_convert_encoding($izh, $out_code, $in_code);
$high = ord($char[0]) - 0x81;
$low = ord($char[1]) - 0x40;
$offset = ($high << 8) + $low - ($high * 0x40);
if ($offset >= 0) {
fseek($fp, $offset * 8, SEEK_SET);
$p_arr = unpack('A8py', fread($fp, 8));
$py = isset($p_arr['py']) ? ($phonetic ? $p_arr['py'] : substr($p_arr['py'], 0, -1)) : '';
if ($first && $i == 0) {
$ret = empty($py[0]) ? '' : $py[0];
break;
}
$ret .= $py;
}
} elseif ($first) {
$ret = $izh;
break;
}
}
fclose($fp);
// 解锁
$redis->del($key);
return $ret;
}
}
做一个加锁和等待锁
多个文件进行优化
由于测试过,一次读取单个拼音要花费14mn,在密集需要导入用户的场景下,速度非常慢。
这里复制了10个文件,采用10个协程跑同一个逻辑,这样就能提高导入速度。
/**
* 获取$string的拼音
* @param string $string 词条字符串
* @param bool $first 是否只取首字母
* @return string
*/
public static function getPY($string, $first = false, $phonetic = false)
{
$count = 10;
// 协程使用该方法会死锁,这里加一个1s的redis锁
start:
static $fileNumList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// static $fileNumList = [0, 1];
if (!empty($fileNumList)) {
$fileNum = array_pop($fileNumList);
} else {
// var_dump(\Hyperf\Utils\Coroutine::id() . ":getPY- 全再用");
$fileNum = rand(0, 10);
}
$key = 'card-service:lock-get-py:' . $fileNum;
$redis = RedisUtil::redis();
if ($redis->incr($key) !== 1) {
$redis->expire($key, 1);
var_dump(\Hyperf\Utils\Coroutine::id() . ":getPY-{$fileNum} 等待锁~");
$count--;
if ($count < 0) {
return '';
}
// 等待 20ms - 40 ms
array_push($fileNumList, $fileNum);
usleep(rand(10000, 20000));
goto start;
} else {
// var_dump("getPY 运行成功");
$redis->expire($key, 1);
// 文件有并发问题,所以多搞几个文件
$pdat = BASE_PATH . '/storage/py' . $fileNum . '.dat'; // 多音字dat数据
$fp = fopen($pdat, 'rb');
if (!$fp) {
array_push($fileNumList, $fileNum);
return '*';
}
$in_code = 'UTF-8';
$out_code = 'GBK';
$strlen = mb_strlen($string, $in_code);
$ret = '';
for ($i = 0; $i < $strlen; $i++) {
$py = '';
$izh = mb_substr($string, $i, 1, $in_code);
if (preg_match('/^[a-zA-Z0-9]$/', $izh)) {
if ($first && $i == 0) {
$ret = $izh;
break;
}
$ret .= $izh;
} elseif (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $izh)) { // 只取纯汉字,其他非汉字符号一概忽略。
$char = mb_convert_encoding($izh, $out_code, $in_code);
$high = ord($char[0]) - 0x81;
$low = ord($char[1]) - 0x40;
$offset = ($high << 8) + $low - ($high * 0x40);
if ($offset >= 0) {
fseek($fp, $offset * 8, SEEK_SET);
$p_arr = unpack('A8py', fread($fp, 8));
$py = isset($p_arr['py']) ? ($phonetic ? $p_arr['py'] : substr($p_arr['py'], 0, -1)) : '';
if ($first && $i == 0) {
$ret = empty($py[0]) ? '' : $py[0];
break;
}
$ret .= $py;
}
} elseif ($first) {
$ret = $izh;
break;
}
}
fclose($fp);
// 解锁
$redis->del($key);
array_push($fileNumList, $fileNum);
return $ret;
}
}