标题: [原创] 批处理并发编程:浅谈锁的实现 [打印本页]
作者: 老刘1号 时间: 2023-10-5 08:26 标题: 批处理并发编程:浅谈锁的实现
本帖最后由 老刘1号 于 2023-10-5 09:01 编辑
数据竞争:我们为什么需要锁?
由变量递增引发的血案- pushd "%temp%"
- for %%i in (inc10*) do del /f /q %%i 2>nul
-
- :: 读取、相加、写回
- (
- echo @echo off
- echo set /a times = 1
- echo :loop
- echo.
- echo :retry1
- echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
- echo set /a n += 1
- echo :retry2
- echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
- echo.
- echo if %%times%% lss 10 ^(
- echo set /a times += 1
- echo goto loop
- echo ^)
- echo exit /b
- )>inc10.cmd
-
- rem type inc10.cmd
复制代码
串行:世界在我掌控- pushd "%temp%"
- echo 0 >share_var
- call inc10.cmd
- call inc10.cmd
- type share_var
复制代码
复制代码
并行:当潘多拉的魔盒打开- pushd "%temp%"
- echo 0 >share_var
- for /f "delims=" %%i in ('start /b "" cmd /c "inc10.cmd" ^& start /b "" cmd /c "inc10.cmd"') do (cd .)
- type share_var
复制代码
复制代码
原因?
下面是一种可能的情形。- 线程1 读取n
- 线程2 读取n
- 线程1 写回n+1
- 线程2 写回n+1
复制代码
导致尽管进行了两次递增,变量却只增大了1。
锁与原子性:解决之道
原子性:不可拆分
一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。
若我们的递增操作具有原子性:- 线程1 递增 n 至 n + 1
- 线程2 递增 n + 1 至 n + 2
复制代码
我们就避免了数据竞争。
Peterson 算法:两个线程的锁实现
考虑如下协议:- A 或 B 想如厕时,首先举起自己手中的旗子,代表自己有如厕的意愿。
- 向厕所门上张贴对方的名字,请对方先上。
- 观察,若对方不想如厕(旗子未举起)、或门上的名字是自己,那么进入如厕。
- 如厕完成后放下旗子。
复制代码
- pushd "%temp%"
- for %%i in (inc10*) do del /f /q %%i 2>nul
- call :save inc10_A.cmd A B
- call :save inc10_B.cmd B A
- fc inc10_A.cmd inc10_B.cmd
- goto :eof
-
- :save <filename> <self> <another>
- set "filename=%1"
- set "self=%2"
- set "another=%3"
- (
- echo @echo off
- echo set /a times = 1
- echo :loop
-
- echo rem 获取锁
- echo cd . ^> flag%self%
- echo :retag
- echo ^(echo %another%^>tag^) 2^>nul || goto retag
- echo :check
- echo if not exist flag%another% goto pass
- echo ^( set /p tag=^<tag ^) 2^>nul || goto check
- echo if "%%tag%%" == "%self%" goto pass
- echo goto check
- echo :pass
- echo.
-
- echo rem 临界区
- echo :retry1
- echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
- echo set /a n += 1
- echo :retry2
- echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
- echo.
-
- echo rem 释放锁
- echo :retry_free_lock
- echo ^( del /f /q flag%self% ^) 2^>nul ^|^| goto retry_free_lock
-
- echo if %%times%% lss 10 ^(
- echo set /a times += 1
- echo goto loop
- echo ^)
- echo exit /b
- )>%filename%
- goto :eof
复制代码
- 正在比较文件 inc10_A.cmd 和 INC10_B.CMD
- ***** inc10_A.cmd
- rem 获取锁
- cd . > flagA
- :retag
- (echo B>tag) 2>nul
- :check
- if not exist flagB goto pass
- ( set /p tag=<tag ) 2>nul
- if "%tag%" == "A" goto pass
- goto check
- ***** INC10_B.CMD
- rem 获取锁
- cd . > flagB
- :retag
- (echo A>tag) 2>nul
- :check
- if not exist flagA goto pass
- ( set /p tag=<tag ) 2>nul
- if "%tag%" == "B" goto pass
- goto check
- *****
-
- ***** inc10_A.cmd
- :retry_free_lock
- ( del /f /q flagA ) 2>nul || goto retry_free_lock
- if %times% lss 10 (
- ***** INC10_B.CMD
- :retry_free_lock
- ( del /f /q flagB ) 2>nul || goto retry_free_lock
- if %times% lss 10 (
- *****
复制代码
- pushd "%temp%"
- del /f /q flagA flagB tag 2>nul
- echo 0 >share_var
- for /f "delims=" %%i in ('start /b "" cmd /c "inc10_A.cmd" ^& start /b "" cmd /c "inc10_B.cmd"') do (cd .)
- type share_var
复制代码
复制代码
Filter 算法:当 Peterson 算法拓展到 N 个线程
考虑如下协议:- 目前有至多 N 位等待如厕。
- 坑位只有一个。
- 设置从左到右 N - 1 个等待位(每个等待位可容纳多人)。
- 每个等待位上有一个牌子,写着:后来者之牌。
-
- 对于每位等待者,从最左边的等待位开始。
- 将等待位牌子上的名字换成自己的。
- 观察,若牌子上的名字还是自己,并且有人在和自己相同或更右边的等待位上,继续等待。
- 否则向右移动一个等待位。
-
- 若不能继续向右移动,
- 那么开始如厕(此时他人看来还保持在最右侧的等待位)。
-
- 如厕完成后离开最右侧的等待位。
复制代码
- pushd "%temp%"
- for %%i in (inc10*) do del /f /q %%i 2>nul
-
- set N=10
- for /l %%i in (1 1 %N%) do (
- call :save inc10_%%i.cmd %%i %N%
- )
- goto :eof
-
- :save <file_name> <self_id> <N>
- set "filename=%1"
- set "self=%2"
- set "N=%3"
- (
- echo @echo off
- echo set /a times = 1
- echo :loop
-
- echo rem 获取锁
- echo setlocal ENABLEDELAYEDEXPANSION
- echo for /l %%%%i in ^( 1 1 %N% ^) do ^(
- echo call :write_var standing_%self% %%%%i
- echo call :write_var last_wait_%%%%i %self%
- echo call :wait %%%%i
- echo ^)
- echo goto pass
- echo :wait ^<level^>
- echo set "level=%%1"
- echo call :read_var "last_wait_^!level^!"
- echo if not "^!re^!" == "%self%" goto :eof
- echo set continue_wait=
- echo for /l %%%%i in ^( 1 1 %N% ^) do ^(
- echo title Thread: %self% level: "^!level^!" check: %%%%i
- echo if not "%%%%i" == "%self%" ^(
- echo call :test_read_var standing_%%%%i -1
- echo set /a is_bigger = re - level
- echo for /f "delims=" %%%%j in ^("^!is_bigger^!"^) do if %%%%j geq 0 ^(
- echo set continue_wait=t
- echo ^)
- echo ^)
- echo ^)
- echo if defined continue_wait ^(
- echo goto wait
- echo ^) else ^(
- echo goto :eof
- echo ^)
- echo :pass
- echo.
-
- echo rem 临界区
- echo :retry1
- echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
- echo set /a n += 1
- echo :retry2
- echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
- echo.
-
- echo rem 释放锁
- echo :retry_free_lock
- echo ^( del /f /q standing_%self% ^) 2^>nul ^|^| goto retry_free_lock
-
- echo if %%times%% lss 10 ^(
- echo set /a times += 1
- echo goto loop
- echo ^)
- echo exit /b
-
-
- echo :read_var ^<var_name^>
- echo ^( set /p re=^<%%1 ^) 2^>nul ^|^| goto read_var
- echo goto :eof
- echo.
-
- echo :test_read_var ^<var_name^> ^<default^>
- echo ^( set /p re=^<%%1 ^) 2^>nul ^|^| set re=%%2
- echo goto :eof
- echo.
-
- echo :write_var ^<var_name^> ^<value^>
- echo ^( ^>%%1 echo %%2^) 2^>nul ^|^| goto write_var
- echo goto :eof
- echo.
-
- )>%filename%
- goto :eof
复制代码
- set N=10
-
- pushd "%temp%"
- echo 0 >share_var
-
- for %%i in (standing_*) do del /f /q %%i 2>nul
- for %%i in (last_wait_*) do del /f /q %%i 2>nul
-
- for /l %%i in (1 1 %N%) do (
- call inc10_%%i.cmd
- )
- type share_var
复制代码
复制代码
- set N=10
-
- pushd "%temp%"
- echo 0 >share_var
-
- for %%i in (standing_*) do del /f /q %%i 2>nul
- for %%i in (last_wait_*) do del /f /q %%i 2>nul
-
- setlocal ENABLEDELAYEDEXPANSION
- set cmd=cd .
- for /l %%i in (1 1 %N%) do (
- set "cmd=!cmd! ^& start /b "" cmd /c "inc10_%%i.cmd""
- )
-
- for /f "delims=" %%i in ('!cmd!') do (cd .)
- type share_var
复制代码
复制代码
文件移动:一种基于文件系统特性的自旋锁
考虑如下协议:- 有若干人需要如厕,但钥匙只有一把。
- 所有人争抢钥匙,抢到钥匙的人如厕,如厕完成后将钥匙放回。
复制代码
- pushd "%temp%"
- for %%i in (inc10*) do del /f /q %%i 2>nul
-
- set N=10
- for /l %%i in (1 1 %N%) do (
- call :save inc10_%%i.cmd %%i
- )
- goto :eof
-
- :save <file_name> <self_id>
- set "filename=%1"
- set "self=%2"
- (
- echo @echo off
- echo set /a times = 1
- echo :loop
-
- echo rem 获取锁
- echo :get_lock
- echo move key hold_key_%self% 2^>nul ^>nul
- echo if not exist hold_key_%self% goto get_lock
- echo :pass
- echo.
-
- echo rem 临界区
- echo :retry1
- echo ^( set /p n=^<share_var ^) 2^>nul ^|^| goto retry1
- echo set /a n += 1
- echo :retry2
- echo ^( echo %%n%% ^>share_var ^) 2^>nul ^|^| goto retry2
- echo.
-
- echo rem 释放锁
- echo :retry_free_lock
- echo ^( move hold_key_%self% key ^) 2^>nul ^>nul ^|^| goto retry_free_lock
-
- echo if %%times%% lss 10 ^(
- echo set /a times += 1
- echo goto loop
- echo ^)
-
- )>%filename%
- goto :eof
复制代码
- pushd "%temp%"
- echo 0 >share_var
-
- cd . >key
- for %%i in (hold_key_*) do del /f /q %%i 2>nul
-
-
- set N=10
- for /l %%i in (1 1 %N%) do (
- call inc10_%%i.cmd
- )
- type share_var
复制代码
复制代码
- pushd "%temp%"
- echo 0 >share_var
-
- cd . >key
- for %%i in (hold_key_*) do del /f /q %%i 2>nul
-
- setlocal ENABLEDELAYEDEXPANSION
- set cmd=cd .
-
- set N=10
- for /l %%i in (1 1 %N%) do (
- set "cmd=!cmd! ^& start /b "" cmd /c "inc10_%%i.cmd""
- )
-
- for /f "delims=" %%i in ('!cmd!') do (cd .)
- type share_var
复制代码
复制代码
其它
本文章由 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 |