Skip to content

Commit beea5f2

Browse files
committed
Add an exclusive parameter to GIL.lock and GIL.@lock
Setting the exclusive parameter to false allow for the old behavior of only depending on Python's GIL mechanisms. Setting the exclusive parameter to false bypasses the ReentrantLock `GIL._jl_gil_lock`.
1 parent 3b6caaa commit beea5f2

File tree

2 files changed

+38
-7
lines changed

2 files changed

+38
-7
lines changed

src/GIL/GIL.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,23 @@ Returns `true` if the current thread has the GIL or `false` otherwise.
2424
hasgil() = C.PyGILState_Check() == Cint(1)
2525

2626
"""
27-
lock(f)
27+
lock(f; exclusive=true)
2828
2929
Lock the GIL, compute `f()`, unlock the GIL, then return the result of `f()`.
3030
3131
Use this to run Python code from threads that do not currently hold the GIL, such as new
3232
threads. Since the main Julia thread holds the GIL by default, you will need to
3333
[`unlock`](@ref) the GIL before using this function.
3434
35+
Setting the `exclusive` keyword to `false` allows more than one Julia `Task`
36+
to attempt to acquire the GIL. This may be useful if one Julia `Task` calls
37+
code which releases the GIL, in which case another Julia task could acquire
38+
it. However, this is probably not a sound approach.
39+
3540
See [`@lock`](@ref) for the macro form.
3641
"""
37-
function lock(f)
38-
Base.lock(_jl_gil_lock)
42+
function lock(f; exclusive=true)
43+
exclusive && Base.lock(_jl_gil_lock)
3944
try
4045
state = C.PyGILState_Ensure()
4146
try
@@ -44,19 +49,24 @@ function lock(f)
4449
C.PyGILState_Release(state)
4550
end
4651
finally
47-
Base.unlock(_jl_gil_lock)
52+
exclusive && Base.unlock(_jl_gil_lock)
4853
end
4954
end
5055

5156
"""
52-
@lock expr
57+
@lock [exclusive=true] expr
5358
5459
Lock the GIL, compute `expr`, unlock the GIL, then return the result of `expr`.
5560
5661
Use this to run Python code from threads that do not currently hold the GIL, such as new
5762
threads. Since the main Julia thread holds the GIL by default, you will need to
5863
[`@unlock`](@ref) the GIL before using this function.
5964
65+
Setting the `exclusive` parameter to `false` allows more than one Julia `Task`
66+
to attempt to acquire the GIL. This may be useful if one Julia `Task` calls
67+
code which releases the GIL, in which case another Julia task could acquire
68+
it. However, this is probably not a sound approach.
69+
6070
The macro equivalent of [`lock`](@ref).
6171
"""
6272
macro lock(expr)
@@ -75,6 +85,27 @@ macro lock(expr)
7585
end
7686
end
7787

88+
macro lock(parameter, expr)
89+
parameter.head == :(=) &&
90+
parameter.args[1] == :exclusive ||
91+
throw(ArgumentError("The only accepted parameter to @lock is `exclusive`."))
92+
93+
do_lock = esc(parameter.args[2])
94+
quote
95+
$do_lock && Base.lock(_jl_gil_lock)
96+
try
97+
state = C.PyGILState_Ensure()
98+
try
99+
$(esc(expr))
100+
finally
101+
C.PyGILState_Release(state)
102+
end
103+
finally
104+
$do_lock && Base.unlock(_jl_gil_lock)
105+
end
106+
end
107+
end
108+
78109
"""
79110
unlock(f)
80111

test/GIL.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
function threaded_sleep()
55
PythonCall.GIL.unlock() do
66
Threads.@threads for i = 1:2
7-
PythonCall.GIL.lock() do
7+
PythonCall.GIL.lock(exclusive=false) do
88
pyimport("time").sleep(1)
99
end
1010
end
@@ -33,7 +33,7 @@ end
3333
# GIL, these can happen in parallel if Julia has at least 2 threads.
3434
function threaded_sleep()
3535
PythonCall.GIL.@unlock Threads.@threads for i = 1:2
36-
PythonCall.GIL.@lock pyimport("time").sleep(1)
36+
PythonCall.GIL.@lock exclusive=false pyimport("time").sleep(1)
3737
end
3838
end
3939
# one run to ensure it's compiled

0 commit comments

Comments
 (0)