Board logo

标题: [原创] 批处理并发编程:浅谈锁的实现 [打印本页]

作者: 老刘1号    时间: 2023-10-5 08:26     标题: 批处理并发编程:浅谈锁的实现

本帖最后由 老刘1号 于 2023-10-5 09:01 编辑

数据竞争:我们为什么需要锁?

由变量递增引发的血案
  1. pushd "%temp%"
  2. for %%i in (inc10*) do del /f /q %%i 2>nul
  3. :: 读取、相加、写回
  4. (
  5.     echo @echo off
  6.     echo set /a times = 1
  7.     echo :loop
  8.     echo.
  9.     echo :retry1
  10.     echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
  11.     echo set /a n += 1
  12.     echo :retry2
  13.     echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
  14.     echo.
  15.     echo if %%times%% lss 10 ^(
  16.     echo     set /a times += 1
  17.     echo     goto loop
  18.     echo ^)
  19.     echo exit /b
  20. )>inc10.cmd
  21. rem type inc10.cmd
复制代码
串行:世界在我掌控
  1. pushd "%temp%"
  2. echo 0 >share_var
  3. call inc10.cmd
  4. call inc10.cmd
  5. type share_var
复制代码
  1. 20  
复制代码
并行:当潘多拉的魔盒打开
  1. pushd "%temp%"
  2. echo 0 >share_var
  3. for /f "delims=" %%i in ('start /b "" cmd /c "inc10.cmd" ^& start /b "" cmd /c "inc10.cmd"') do (cd .)
  4. type share_var
复制代码
  1. 12  
复制代码
原因?

下面是一种可能的情形。
  1. 线程1 读取n
  2. 线程2 读取n
  3. 线程1 写回n+1
  4. 线程2 写回n+1
复制代码
导致尽管进行了两次递增,变量却只增大了1

锁与原子性:解决之道

原子性:不可拆分

一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。

若我们的递增操作具有原子性:
  1. 线程1 递增 n    至 n + 1
  2. 线程2 递增 n + 1 至 n + 2
复制代码
我们就避免了数据竞争。

Peterson 算法:两个线程的锁实现

考虑如下协议:
  1. A 或 B 想如厕时,首先举起自己手中的旗子,代表自己有如厕的意愿。
  2. 向厕所门上张贴对方的名字,请对方先上。
  3. 观察,若对方不想如厕(旗子未举起)、或门上的名字是自己,那么进入如厕。
  4. 如厕完成后放下旗子。
复制代码
  1. pushd "%temp%"
  2. for %%i in (inc10*) do del /f /q %%i 2>nul
  3. call :save inc10_A.cmd A B
  4. call :save inc10_B.cmd B A
  5. fc inc10_A.cmd inc10_B.cmd
  6. goto :eof
  7. :save <filename> <self> <another>
  8. set "filename=%1"
  9. set "self=%2"
  10. set "another=%3"
  11. (
  12.     echo @echo off
  13.     echo set /a times = 1
  14.     echo :loop
  15.     echo rem 获取锁
  16.     echo cd . ^> flag%self%
  17.     echo :retag
  18.     echo ^(echo %another%^>tag^) 2^>nul || goto retag
  19.     echo :check
  20.     echo     if not exist flag%another% goto pass
  21.     echo     ^( set /p tag=^<tag ^) 2^>nul || goto check
  22.     echo     if "%%tag%%" == "%self%" goto pass
  23.     echo     goto check
  24.     echo :pass
  25.     echo.
  26.     echo rem 临界区
  27.     echo :retry1
  28.     echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
  29.     echo set /a n += 1
  30.     echo :retry2
  31.     echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
  32.     echo.
  33.     echo rem 释放锁
  34.     echo :retry_free_lock
  35.     echo ^( del /f /q flag%self% ^) 2^>nul ^|^| goto retry_free_lock
  36.     echo if %%times%% lss 10 ^(
  37.     echo     set /a times += 1
  38.     echo     goto loop
  39.     echo ^)
  40.     echo exit /b
  41. )>%filename%
  42. goto :eof
复制代码
  1. 正在比较文件 inc10_A.cmd 和 INC10_B.CMD
  2. ***** inc10_A.cmd
  3. rem 获取锁
  4. cd . > flagA
  5. :retag
  6. (echo B>tag) 2>nul
  7. :check
  8.     if not exist flagB goto pass
  9.     ( set /p tag=<tag ) 2>nul
  10.     if "%tag%" == "A" goto pass
  11.     goto check
  12. ***** INC10_B.CMD
  13. rem 获取锁
  14. cd . > flagB
  15. :retag
  16. (echo A>tag) 2>nul
  17. :check
  18.     if not exist flagA goto pass
  19.     ( set /p tag=<tag ) 2>nul
  20.     if "%tag%" == "B" goto pass
  21.     goto check
  22. *****
  23. ***** inc10_A.cmd
  24. :retry_free_lock
  25. ( del /f /q flagA ) 2>nul || goto retry_free_lock
  26. if %times% lss 10 (
  27. ***** INC10_B.CMD
  28. :retry_free_lock
  29. ( del /f /q flagB ) 2>nul || goto retry_free_lock
  30. if %times% lss 10 (
  31. *****
复制代码
  1. pushd "%temp%"
  2. del /f /q flagA flagB tag 2>nul
  3. echo 0 >share_var
  4. for /f "delims=" %%i in ('start /b "" cmd /c "inc10_A.cmd" ^& start /b "" cmd /c "inc10_B.cmd"') do (cd .)
  5. type share_var
复制代码
  1. 20  
复制代码
Filter 算法:当 Peterson 算法拓展到 N 个线程

考虑如下协议:
  1. 目前有至多 N 位等待如厕。
  2. 坑位只有一个。
  3. 设置从左到右 N - 1 个等待位(每个等待位可容纳多人)。
  4. 每个等待位上有一个牌子,写着:后来者之牌。
  5. 对于每位等待者,从最左边的等待位开始。
  6. 将等待位牌子上的名字换成自己的。
  7. 观察,若牌子上的名字还是自己,并且有人在和自己相同或更右边的等待位上,继续等待。
  8. 否则向右移动一个等待位。
  9. 若不能继续向右移动,
  10. 那么开始如厕(此时他人看来还保持在最右侧的等待位)。
  11. 如厕完成后离开最右侧的等待位。
复制代码
  1. pushd "%temp%"
  2. for %%i in (inc10*) do del /f /q %%i 2>nul
  3. set N=10
  4. for /l %%i in (1 1 %N%) do (
  5.     call :save inc10_%%i.cmd %%i %N%
  6. )
  7. goto :eof
  8. :save <file_name> <self_id> <N>
  9. set "filename=%1"
  10. set "self=%2"
  11. set "N=%3"
  12. (
  13.     echo @echo off
  14.     echo set /a times = 1
  15.     echo :loop
  16.     echo rem 获取锁
  17.     echo setlocal ENABLEDELAYEDEXPANSION
  18.     echo for /l %%%%i in ^( 1 1 %N% ^) do ^(
  19.     echo     call :write_var standing_%self% %%%%i
  20.     echo     call :write_var last_wait_%%%%i %self%
  21.     echo     call :wait %%%%i
  22.     echo ^)
  23.     echo goto pass
  24.     echo :wait ^<level^>
  25.     echo     set "level=%%1"
  26.     echo     call :read_var "last_wait_^!level^!"
  27.     echo     if not "^!re^!" == "%self%" goto :eof
  28.     echo     set continue_wait=
  29.     echo     for /l %%%%i in ^( 1 1 %N% ^) do ^(
  30.     echo         title Thread: %self% level: "^!level^!" check: %%%%i
  31.     echo         if not "%%%%i" == "%self%" ^(
  32.     echo             call :test_read_var standing_%%%%i -1
  33.     echo             set /a is_bigger = re - level
  34.     echo             for /f "delims=" %%%%j in ^("^!is_bigger^!"^) do if %%%%j geq 0 ^(
  35.     echo                     set continue_wait=t
  36.     echo             ^)
  37.     echo         ^)
  38.     echo     ^)
  39.     echo     if defined continue_wait ^(
  40.     echo         goto wait
  41.     echo     ^) else ^(
  42.     echo         goto :eof
  43.     echo     ^)
  44.     echo :pass
  45.     echo.
  46.     echo rem 临界区
  47.     echo :retry1
  48.     echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
  49.     echo set /a n += 1
  50.     echo :retry2
  51.     echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
  52.     echo.
  53.     echo rem 释放锁
  54.     echo :retry_free_lock
  55.     echo ^( del /f /q standing_%self% ^) 2^>nul ^|^| goto retry_free_lock
  56.     echo if %%times%% lss 10 ^(
  57.     echo     set /a times += 1
  58.     echo     goto loop
  59.     echo ^)
  60.     echo exit /b
  61.     echo :read_var ^<var_name^>
  62.     echo ^( set /p re=^<%%1 ^) 2^>nul ^|^| goto read_var
  63.     echo goto :eof
  64.     echo.
  65.     echo :test_read_var ^<var_name^> ^<default^>
  66.     echo ^( set /p re=^<%%1 ^) 2^>nul ^|^| set re=%%2
  67.     echo goto :eof
  68.     echo.
  69.     echo :write_var ^<var_name^> ^<value^>
  70.     echo ^( ^>%%1 echo %%2^) 2^>nul ^|^| goto write_var
  71.     echo goto :eof
  72.     echo.
  73. )>%filename%
  74. goto :eof
复制代码
  1. set N=10
  2. pushd "%temp%"
  3. echo 0 >share_var
  4. for %%i in (standing_*) do del /f /q %%i 2>nul
  5. for %%i in (last_wait_*) do del /f /q %%i 2>nul
  6. for /l %%i in (1 1 %N%) do (
  7.     call inc10_%%i.cmd
  8. )
  9. type share_var
复制代码
  1. 100  
复制代码
  1. set N=10
  2. pushd "%temp%"
  3. echo 0 >share_var
  4. for %%i in (standing_*) do del /f /q %%i 2>nul
  5. for %%i in (last_wait_*) do del /f /q %%i 2>nul
  6. setlocal ENABLEDELAYEDEXPANSION
  7. set cmd=cd .
  8. for /l %%i in (1 1 %N%) do (
  9.     set "cmd=!cmd! ^& start /b "" cmd /c "inc10_%%i.cmd""
  10. )
  11. for /f "delims=" %%i in ('!cmd!') do (cd .)
  12. type share_var
复制代码
  1. 100  
复制代码
文件移动:一种基于文件系统特性的自旋锁

考虑如下协议:
  1. 有若干人需要如厕,但钥匙只有一把。
  2. 所有人争抢钥匙,抢到钥匙的人如厕,如厕完成后将钥匙放回。
复制代码
  1. pushd "%temp%"
  2. for %%i in (inc10*) do del /f /q %%i 2>nul
  3. set N=10
  4. for /l %%i in (1 1 %N%) do (
  5.     call :save inc10_%%i.cmd %%i
  6. )
  7. goto :eof
  8. :save <file_name> <self_id>
  9. set "filename=%1"
  10. set "self=%2"
  11. (
  12.     echo @echo off
  13.     echo set /a times = 1
  14.     echo :loop
  15.     echo rem 获取锁
  16.     echo :get_lock
  17.     echo move key hold_key_%self% 2^>nul ^>nul
  18.     echo if not exist hold_key_%self% goto get_lock
  19.     echo :pass
  20.     echo.
  21.     echo rem 临界区
  22.     echo :retry1
  23.     echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
  24.     echo set /a n += 1
  25.     echo :retry2
  26.     echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
  27.     echo.
  28.     echo rem 释放锁
  29.     echo :retry_free_lock
  30.     echo ^( move hold_key_%self% key ^) 2^>nul ^>nul ^|^| goto retry_free_lock
  31.     echo if %%times%% lss 10 ^(
  32.     echo     set /a times += 1
  33.     echo     goto loop
  34.     echo ^)
  35. )>%filename%
  36. goto :eof
复制代码
  1. pushd "%temp%"
  2. echo 0 >share_var
  3. cd . >key
  4. for %%i in (hold_key_*) do del /f /q %%i 2>nul
  5. set N=10
  6. for /l %%i in (1 1 %N%) do (
  7.     call inc10_%%i.cmd
  8. )
  9. type share_var
复制代码
  1. 100  
复制代码
  1. pushd "%temp%"
  2. echo 0 >share_var
  3. cd . >key
  4. for %%i in (hold_key_*) do del /f /q %%i 2>nul
  5. setlocal ENABLEDELAYEDEXPANSION
  6. set cmd=cd .
  7. set N=10
  8. for /l %%i in (1 1 %N%) do (
  9.     set "cmd=!cmd! ^& start /b "" cmd /c "inc10_%%i.cmd""
  10. )
  11. for /f "delims=" %%i in ('!cmd!') do (cd .)
  12. type share_var
复制代码
  1. 100  
复制代码
其它

本文章由 Jupyter Notebook 及 iBatch Kernel 强力驱动。
作者: hlzj88    时间: 2023-10-5 11:26

谢谢举例讲解。我前几天的问题就是这样的一把钥匙,n人要用的情况。
作者: czjt1234    时间: 2023-10-6 07:58

老大这是火气上来了




欢迎光临 批处理之家 (http://www.bathome.net/) Powered by Discuz! 7.2