想象一下我有一个复杂的测试用例,它偶尔会导致两个线程死锁。我认为,在 CI 中运行来防止并发 bug 是不合适的。我想编写一个简单的测试用例,根据线程的堆栈跟踪,很有可能导致死锁。
但是,由于操作系统中线程的调度,在我的情况下仍然很难重现死锁。在修复错误之前,如何确保在我的情况下几乎总是发生死锁? 我不知道是否有一些技巧可以实现它,例如更改操作系统参数,设置特定的屈服点或休眠点。
此方法适用于 MS Windows,但逻辑应该是可移植的。此代码的不可移植部分是文件打开、文件锁定、文件读取和睡眠 API。
有关 Linux 文件锁定的帮助:使用 c/c++ 在 linux 中锁定文件
逻辑:
进程 1 打开文件 A.txt 并锁定其他进程读取它,然后休眠/等待进程 2 开始。
进程2立即打开B.txt,锁定其他进程读取它,然后也打开文件A.txt并尝试读取它。
当然,Process2 无法读取 A.txt,因为它被 Process1 锁定了。
Process1 从睡眠中醒来,打开文件 B.txt 并尝试读取它 - Process1 无法读取 B.txt,因为 Process2 已锁定它。
两个进程在完成各自的读取后都会愉快地解锁各自第一个打开的文件,但都无法读取,并且两个进程将保持死锁,直到至少一个进程被杀死或成功完成。
下面是一些基于 Windows 的代码(抱歉),用于创建两个进程/可执行文件。
注意:根据您正在编译的进程/exe 将 PROCESS1 重新定义为零或 1。
#include <windows.h>
#include <stdio.h>
#define PROCESS1 1
int main()
{
#if PROCESS1
char *process = "PROCESS 1";
char *fname1 = "A.txt", *fname2 = "B.txt";
DWORD MAX_TRIES = 15;
#else
char *process = "PROCESS 2";
char *fname1 = "B.txt", *fname2 = "A.txt";
DWORD MAX_TRIES = 10;
#endif //PROCESS1
HANDLE fp1 = CreateFile(fname1, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE fp2 = NULL;
DWORD bytesR =0, count=0;
char buff[100];
if (!fp1)
{
printf("%s cannot open %s.\n", process, fname1);
return 0;
}
printf("%s successfully opened File %s.\n", process, fname1);
printf("%s %s lock file %s\n",
process, LockFile(fp1,0,0,1024,0)?"DID":"DID NOT", fname1 ); /* Lock 1024 bytes */
#if PROCESS1
printf("%s sleeping...start process2 within 5 seconds\n", process);
/* process2.exe will immediately open "B.txt" */
Sleep(5000); /* Ensure process2.exe had time to open and lock "B.txt" */
printf("%s awake from sleep()...\n", process);
#endif //PROCESS1
fp2 = CreateFile(fname2, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if(!fp2)
{
printf("%s cannot open %s.\n", process, fname2);
return 0;
}
printf("%s successfully opened File %s.\n", process, fname2);
while(!bytesR && count++ < MAX_TRIES)
{
printf("%s trying to read %s\n", process, fname2); /* Keep trying to read 2nd file */
ReadFile(fp2, buff, 2, &bytesR, NULL);
Sleep(2000);
}
if(bytesR)
printf("%s successfully read %d bytes from file %s...exiting\n", process, bytesR, fname2);
else
printf("%s FAILED to read %d bytes from file %s...exiting\n", process, bytesR, fname2);
UnlockFile(fp1,0,0,1024,0);
system("pause");
return 0;
}
此代码在Windows下测试,2个进程单独运行时输出如下:
PROCESS 1 successfully opened File A.txt.
PROCESS 1 DID lock file A.txt
PROCESS 1 sleeping...start process2 within 5 seconds
PROCESS 1 awake from sleep()...
PROCESS 1 successfully opened File B.txt.
PROCESS 1 trying to read B.txt
PROCESS 1 successfully read 2 bytes from file B.txt...exiting
PROCESS 2 successfully opened File B.txt.
PROCESS 2 DID lock file B.txt
PROCESS 2 successfully opened File A.txt.
PROCESS 2 trying to read A.txt
PROCESS 2 successfully read 2 bytes from file A.txt...exiting
一起运行时的进程输出...进程 2 在进程 1 期间并按照进程 1 的指令启动...它们有一个可怕的死锁:
PROCESS 1 successfully opened File A.txt.
PROCESS 1 DID lock file A.txt
PROCESS 1 sleeping...start process2 within 5 seconds
PROCESS 1 awake from sleep()...
PROCESS 1 successfully opened File B.txt.
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
PROCESS 1 trying to read B.txt
...
PROCESS 2 successfully opened File B.txt.
PROCESS 2 DID lock file B.txt
PROCESS 2 successfully opened File A.txt.
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
PROCESS 2 trying to read A.txt
...