<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40"><head><meta http-equiv=Content-Type content="text/html; charset=utf-8"><meta name=Generator content="Microsoft Word 15 (filtered medium)"><style><!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0in;
font-size:11.0pt;
font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:blue;
text-decoration:underline;}
.MsoChpDefault
{mso-style-type:export-only;}
@page WordSection1
{size:8.5in 11.0in;
margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
{page:WordSection1;}
--></style></head><body lang=EN-US link=blue vlink="#954F72" style='word-wrap:break-word'><div class=WordSection1><p class=MsoNormal>I don’t think it that strange. You always need the GIL before you interact with Python code. The lock is a python object, and so you need the GIL. </p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>You could redesign your code to always use different bit generators so that you would never use the same one, in which case you wouldn’t need to worry about the lock.</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>I also think that the lock only matters for Multithreaded code not Multiprocess. I believe the latter pickles and unpickles any Generator object (and the underying BitGenerator) and so each process has its own version. Note that when multiprocessing the recommended procedure is to use spawn() to generate a sequence of BitGenerators and to use a distinct BitGenerator in each process. If you do this then you are free from the lock.</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>Kevin</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal><o:p> </o:p></p><div style='mso-element:para-border-div;border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0in 0in 0in'><p class=MsoNormal style='border:none;padding:0in'><b>From: </b><a href="mailto:evgeny.burovskiy@gmail.com">Evgeni Burovski</a><br><b>Sent: </b>Monday, December 14, 2020 2:10 PM<br><b>To: </b><a href="mailto:numpy-discussion@python.org">Discussion of Numerical Python</a><br><b>Subject: </b>Re: [Numpy-discussion] locking np.random.Generator in a cython nogil context?</p></div><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>On Mon, Dec 14, 2020 at 4:46 PM Kevin Sheppard</p><p class=MsoNormal><kevin.k.sheppard@gmail.com> wrote:</p><p class=MsoNormal>><o:p> </o:p></p><p class=MsoNormal>> You need to reacquire the gil, then you can get the lock and rerelease the gil.</p><p class=MsoNormal>><o:p> </o:p></p><p class=MsoNormal>> I think this works (on phone, so untested)</p><p class=MsoNormal>><o:p> </o:p></p><p class=MsoNormal>> with gil:</p><p class=MsoNormal>> with nogil, lock:</p><p class=MsoNormal>> ..</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>Thanks Kevin.</p><p class=MsoNormal>This surely works, but feels seriously weird. Is this the only / the</p><p class=MsoNormal>recommended way?</p><p class=MsoNormal>I can of course adjust the buffer size to amortize the time for the</p><p class=MsoNormal>GIL manipulation, but this really starts looking like a code smell.</p><p class=MsoNormal>My original motivation was to have inner simulation loops python-free.</p><p class=MsoNormal>Most likely, the issue is that I'm not using the Generator correctly?</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>Evgeni</p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal><o:p> </o:p></p><p class=MsoNormal>> On Mon, Dec 14, 2020, 13:37 Evgeni Burovski <evgeny.burovskiy@gmail.com> wrote:</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> Hi,</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> What would be the correct way of locking the bit generator of</p><p class=MsoNormal>>> np.random.Generator in cython's nogil context?</p><p class=MsoNormal>>> (This is threading 101, surely, so please forgive my ignorance).</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> The docs for extending np.random.Generator in cython</p><p class=MsoNormal>>> (https://numpy.org/doc/stable/reference/random/extending.html#cython)</p><p class=MsoNormal>>> recommend the following idiom for generating uniform variates, where</p><p class=MsoNormal>>> the GIL is released and a Generator-specific lock is held:</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> x = PCG64()</p><p class=MsoNormal>>> rng = <bitgen_t *> PyCapsule_GetPointer(x.capsule, capsule_name)</p><p class=MsoNormal>>> with nogil, x.lock:</p><p class=MsoNormal>>> rng.next_double(rng.state)</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> What is the correct way of locking it when already in the nogil</p><p class=MsoNormal>>> section (so that x.lock is not accessible)?</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> The use case is a long-running MC process which generates random</p><p class=MsoNormal>>> variates in a tight loop (so the loop is all nogil). In case it</p><p class=MsoNormal>>> matters, I probably won't be using python threads, but may use</p><p class=MsoNormal>>> multiprocessing.</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> Basically,</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> cdef double uniform(self) nogil:</p><p class=MsoNormal>>> if self.idx >= self.buf.shape[0]:</p><p class=MsoNormal>>> self._fill()</p><p class=MsoNormal>>> cdef double value = self.buf[self.idx]</p><p class=MsoNormal>>> self.idx += 1</p><p class=MsoNormal>>> return value</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> cdef void _fill(self) nogil:</p><p class=MsoNormal>>> self.idx = 0</p><p class=MsoNormal>>> # HERE: Lock ?</p><p class=MsoNormal>>> for i in range(self.buf.shape[0]):</p><p class=MsoNormal>>> self.buf[i] = self.rng.next_double(self.rng.state)</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> Thanks,</p><p class=MsoNormal>>> Evgeni</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> P.S. The full cdef class, for completeness:</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> cdef class RndmWrapper():</p><p class=MsoNormal>>> cdef:</p><p class=MsoNormal>>> double[::1] buf</p><p class=MsoNormal>>> Py_ssize_t idx</p><p class=MsoNormal>>> bitgen_t *rng</p><p class=MsoNormal>>> object py_gen # keep the garbage collector away</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> def __init__(self, seed=(1234, 0), buf_size=4096, bitgen_kind=None):</p><p class=MsoNormal>>> if bitgen_kind is None:</p><p class=MsoNormal>>> bitgen_kind = PCG64</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> # cf Numpy-discussion list, K.~Sheppard, R.~Kern, June 29,</p><p class=MsoNormal>>> 2020 and below</p><p class=MsoNormal>>> # https://mail.python.org/pipermail/numpy-discussion/2020-June/080794.html</p><p class=MsoNormal>>> entropy, num = seed</p><p class=MsoNormal>>> seed_seq = SeedSequence(entropy, spawn_key=(num,))</p><p class=MsoNormal>>> py_gen = bitgen_kind(seed_seq)</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> # store the python object to avoid it being garbage collected</p><p class=MsoNormal>>> self.py_gen = py_gen</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> capsule = py_gen.capsule</p><p class=MsoNormal>>> self.rng = <bitgen_t *>PyCapsule_GetPointer(capsule, capsule_name)</p><p class=MsoNormal>>> if not PyCapsule_IsValid(capsule, capsule_name):</p><p class=MsoNormal>>> raise ValueError("Invalid pointer to anon_func_state")</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> self.buf = np.empty(buf_size, dtype='float64')</p><p class=MsoNormal>>> self._fill()</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> @cython.boundscheck(False)</p><p class=MsoNormal>>> @cython.wraparound(False)</p><p class=MsoNormal>>> cdef void _fill(self) nogil:</p><p class=MsoNormal>>> self.idx = 0</p><p class=MsoNormal>>> for i in range(self.buf.shape[0]):</p><p class=MsoNormal>>> self.buf[i] = self.rng.next_double(self.rng.state)</p><p class=MsoNormal>>><o:p> </o:p></p><p class=MsoNormal>>> @cython.boundscheck(False)</p><p class=MsoNormal>>> @cython.wraparound(False)</p><p class=MsoNormal>>> cdef double uniform(self) nogil:</p><p class=MsoNormal>>> if self.idx >= self.buf.shape[0]:</p><p class=MsoNormal>>> self._fill()</p><p class=MsoNormal>>> cdef double value = self.buf[self.idx]</p><p class=MsoNormal>>> self.idx += 1</p><p class=MsoNormal>>> return value</p><p class=MsoNormal>>> _______________________________________________</p><p class=MsoNormal>>> NumPy-Discussion mailing list</p><p class=MsoNormal>>> NumPy-Discussion@python.org</p><p class=MsoNormal>>> https://mail.python.org/mailman/listinfo/numpy-discussion</p><p class=MsoNormal>><o:p> </o:p></p><p class=MsoNormal>> _______________________________________________</p><p class=MsoNormal>> NumPy-Discussion mailing list</p><p class=MsoNormal>> NumPy-Discussion@python.org</p><p class=MsoNormal>> https://mail.python.org/mailman/listinfo/numpy-discussion</p><p class=MsoNormal>_______________________________________________</p><p class=MsoNormal>NumPy-Discussion mailing list</p><p class=MsoNormal>NumPy-Discussion@python.org</p><p class=MsoNormal>https://mail.python.org/mailman/listinfo/numpy-discussion</p><p class=MsoNormal><o:p> </o:p></p></div></body></html>