PHP 中多进程同时处理一个文件 (加锁中出现的问题)

之前有一道面试题,要我手写一个程序,来处理多进程处理文件。细想了很久,但是想得不全面,之前没有怎么用过这些函数,所以没记住这些函数名字,最后导致没写出来,有点心有不甘。查找资料后,发现有挺多中情况的,要一口气把所有情况撸下来,也不容易呀!

PHP 中多进程同时处理一个文件(加锁中出现的问题)

如果同时有两个进程同时写入一个文件(可能会导致文件插入乱序)
那么当一个进程写入文件的时候,就要告诉另外一个进程,我在写入这个文件,你等我操作完再操作。

pcntl_fork() 函数说明

pcntl_fork() 函数是 php-pcntl 模块中用于创建进程的函数。(不支持windows)

  • pcntl_fork() 创建子进程成功后,在父进程内,返回0,在子进程内返回自身的进程号,失败则返回-1

  • 子进程会复制父进程的代码、数据。那么就说明:子,父进程拥有的代码和数据会一模一样。

  • 重点:子进程会复制父进程的状态,也就是说 pcntl_fork() 之前的声明和赋值的变量是在子进程中存在的。

1
2
3
4
for ($i = 0; $i < 3; $i++) {
$pid = pcntl_fork();
}
sleep(30);

上面这个代码一个有 7 个子进程,父进程创建了 3 个子进程,第一个子进程创建了 2 个子进程,第二个子进程创建 1 个进程,第一个子进程的子进程又创建 1 个子进程。

子进程与父进程同一把锁

因为是先打开文件取得句柄,子进程和父进程都是使用同一个句柄,
子进程与父进程持有同一把锁
若子进程已经加锁,父进程又加锁,不会报错,只是更新加锁的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$fd = fopen('lock.txt', 'w+');
flock($fd, LOCK_EX);
if (pcntl_fork() == 0) { // 子进程与父进程持有同一把锁
flock($fd, LOCK_UN); // 同时释放了父子进程持有的锁
}

// $fp = fopen("demo.log", "a");
// $pid = pcntl_fork();
//
// if ($pid == 0) {
// if (flock($fp, LOCK_EX)) {
// echo "子进程加锁成功\n";
// while (1) {
// sleep(1);
// }
// }
// } elseif ($pid > 0) {
// sleep(1);
// if (flock($fp, LOCK_EX)) {
// echo "父进程加锁成功\n";
// }
// }

若不是同用一个文件句柄,同时给一个文件加锁会产生阻塞

fopen 打开两次同一文件或者先 pcntl_fork() 子程序,然后在 fopen 文件
会以不同方式产生句柄,所以是两个不同的锁,如果同时加锁,会产生阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function block_by_itself()
{
$fd1 = fopen('lock.txt', 'w+');
$fd2 = fopen('lock.txt', 'w+');

flock($fd1, LOCK_EX);
flock($fd2, LOCK_EX); // 阻塞

/**
*
* $pid = pcntl_fork();
* $fp = fopen('lock.txt', 'w+');
* flock($fd1, LOCK_EX);
* flock($fd2, LOCK_EX);
*/
}

多进程不用 flock() 同时向一个文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
$pid = pcntl_fork();
$fp = fopen("./no_flock.txt", "a");

if ($pid == 0) {
for ($i = 0; $i < 1000; $i++) {
fwrite($fp, "黄河远上白云间,");
fflush($fp);
fwrite($fp, "一片孤城万仞山。");
fflush($fp);
fwrite($fp, "羌笛何须怨杨柳,");
fflush($fp);
fwrite($fp, "春风不度玉门关。\n");
fflush($fp);
}
} else if ($pid > 0) {
for ($i = 0; $i < 1000; $i++) {
fwrite($fp, "葡萄美酒夜光杯,");
fflush($fp);
fwrite($fp, "欲饮琵琶马上催。");
fflush($fp);
fwrite($fp, "醉卧沙场君莫笑,");
fflush($fp);
fwrite($fp, "古来征战几人回。\n");
fflush($fp);
}
}

多进程使用 flock() 同时向一个文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
$pid = pcntl_fork();
$fp = fopen("./flock.txt", "a");

if ($pid == 0) {
for ($i = 0; $i < 20; $i++) {
if (flock($fp, LOCK_EX)) {
fwrite($fp, "黄河远上白云间,");
fflush($fp);
fwrite($fp, "一片孤城万仞山。");
fflush($fp);
fwrite($fp, "羌笛何须怨杨柳,");
fflush($fp);
fwrite($fp, "春风不度玉门关。\n");
flock($fp, LOCK_UN);
}
}
} elseif ($pid > 0) {
for ($i = 0; $i < 20; $i++) {
if (flock($fp, LOCK_EX)) {
fwrite($fp, "葡萄美酒夜光杯,");
fflush($fp);
fwrite($fp, "欲饮琵琶马上催。");
fflush($fp);
fwrite($fp, "醉卧沙场君莫笑,");
fflush($fp);
fwrite($fp, "古来征战几人回。\n");
fflush($fp);
flock($fp, LOCK_UN);
}
}
}

文章对你有用?给博主一个支持
0%