-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathGIL.jl
More file actions
175 lines (146 loc) · 4.68 KB
/
GIL.jl
File metadata and controls
175 lines (146 loc) · 4.68 KB
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""
module PythonCall.GIL
Handling the Python Global Interpreter Lock.
See [`lock`](@ref), [`@lock`](@ref), [`unlock`](@ref) and [`@unlock`](@ref).
"""
module GIL
using ..C: C
# Ensure that only one Julia task tries to acquire the Python GIL.
# Avoid the potential issue that a task could miscompute whether
# it actually has the GIL simply because a different task that ran
# on the same thread that once had the GIL.
# https://github.com/JuliaPy/PythonCall.jl/issues/627
const _jl_gil_lock = ReentrantLock()
"""
hasgil()
Returns `true` if the current thread has the GIL or `false` otherwise.
"""
hasgil() = C.PyGILState_Check() == Cint(1)
"""
lock(f; exclusive=true)
Lock the GIL, compute `f()`, unlock the GIL, then return the result of `f()`.
Use this to run Python code from threads that do not currently hold the GIL, such as new
threads. Since the main Julia thread holds the GIL by default, you will need to
[`unlock`](@ref) the GIL before using this function.
Setting the `exclusive` keyword to `false` allows more than one Julia `Task`
to attempt to acquire the GIL. This may be useful if one Julia `Task` calls
code which releases the GIL, in which case another Julia task could acquire
it. However, this is probably not a sound approach.
See [`@lock`](@ref) for the macro form.
"""
function lock(f; exclusive=true)
exclusive && Base.lock(_jl_gil_lock)
try
state = C.PyGILState_Ensure()
try
f()
finally
C.PyGILState_Release(state)
end
finally
exclusive && Base.unlock(_jl_gil_lock)
end
end
"""
@lock [exclusive=true] expr
Lock the GIL, compute `expr`, unlock the GIL, then return the result of `expr`.
Use this to run Python code from threads that do not currently hold the GIL, such as new
threads. Since the main Julia thread holds the GIL by default, you will need to
[`@unlock`](@ref) the GIL before using this function.
Setting the `exclusive` parameter to `false` allows more than one Julia `Task`
to attempt to acquire the GIL. This may be useful if one Julia `Task` calls
code which releases the GIL, in which case another Julia task could acquire
it. However, this is probably not a sound approach.
The macro equivalent of [`lock`](@ref).
"""
macro lock(expr)
quote
Base.lock(_jl_gil_lock)
try
state = C.PyGILState_Ensure()
try
$(esc(expr))
finally
C.PyGILState_Release(state)
end
finally
Base.unlock(_jl_gil_lock)
end
end
end
macro lock(parameter, expr)
parameter.head == :(=) &&
parameter.args[1] == :exclusive ||
throw(ArgumentError("The only accepted parameter to @lock is `exclusive`."))
do_lock = esc(parameter.args[2])
quote
$do_lock && Base.lock(_jl_gil_lock)
try
state = C.PyGILState_Ensure()
try
$(esc(expr))
finally
C.PyGILState_Release(state)
end
finally
$do_lock && Base.unlock(_jl_gil_lock)
end
end
end
"""
unlock(f)
Unlock the GIL, compute `f()`, re-lock the GIL, then return the result of `f()`.
Use this to run non-Python code with the GIL unlocked, so allowing another thread to run
Python code. That other thread can be a Julia thread, which must lock the GIL using
[`lock`](@ref).
See [`@unlock`](@ref) for the macro form.
"""
function unlock(f)
_locked = Base.islocked(_jl_gil_lock)
_locked && Base.unlock(_jl_gil_lock)
try
state = C.PyEval_SaveThread()
try
f()
finally
C.PyEval_RestoreThread(state)
end
finally
_locked && Base.lock(_jl_gil_lock)
end
end
"""
@unlock expr
Unlock the GIL, compute `expr`, re-lock the GIL, then return the result of `expr`.
Use this to run non-Python code with the GIL unlocked, so allowing another thread to run
Python code. That other thread can be a Julia thread, which must lock the GIL using
[`@lock`](@ref).
The macro equivalent of [`unlock`](@ref).
"""
macro unlock(expr)
quote
_locked = Base.islocked(_jl_gil_lock)
_locked && Base.unlock(_jl_gil_lock)
try
state = C.PyEval_SaveThread()
try
$(esc(expr))
finally
C.PyEval_RestoreThread(state)
end
finally
_locked && Base.lock(_jl_gil_lock)
end
end
end
#=
# Disable this for now since holding this lock will disable finalizers
# If the main thread already has the GIL, we should lock _jl_gil_lock.
function __init__()
if hasgil()
Base.lock(_jl_gil_lock)
end
end
=#
include("GlobalInterpreterLock.jl")
end