Skip to content

Commit 3617758

Browse files
authored
Merge pull request #482 from esheldon/fix-docs
feat: clarify docs on threads and add a test
2 parents ceaeb81 + 666be71 commit 3617758

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,15 +488,41 @@ the ASCII character set.
488488
`fitsio` is a Python wrapper for the `cfitsio` library and so inherits the constraints
489489
on multithreaded programs from `cfitsio`. Specifically this means that
490490

491-
- `fitsio.FITS` file objects are NOT thread-safe and should never be shared between threads.
492491
- Concurrent reading from FITS files is thread-safe, but every thread must open the FITS file
493-
on its own, getting unique `fitsio.FITS` object.
492+
on its own, getting a unique `fitsio.FITS` object.
494493
- Concurrent writing to FITS files is NOT thread-safe.
494+
- `fitsio.FITS` file objects can be shared between threads for reading, but only one thread
495+
can use the file object at a time and so use needs to be controlled via a lock or some
496+
other mechanism. See the example below.
495497

496498
`fitsio` is compatible with Python free threading, and will not reenable the GIL
497499
when imported. However, the constraints above must be respected even when using Python
498500
free threading.
499501

502+
Here is an example of using a lock to share a `fitsio.FITS` file pointer across threads:
503+
504+
```python
505+
import concurrent.futures
506+
import threading
507+
import fitsio
508+
509+
510+
lock = threading.RLock()
511+
512+
def _read_file(fp):
513+
with lock:
514+
# do something with fp here
515+
pass
516+
517+
with fitsio.FITS(fname) as fp:
518+
with ThreadPoolExecutor(max_workers=10) as exc:
519+
futs = [
520+
exc.submit(_read_file, fp) for _ in range(10)
521+
]
522+
for fut in futs:
523+
res = fut.result()
524+
```
525+
500526
## TODO
501527

502528
- HDU groups: does anyone use these? If so open an issue!

fitsio/tests/test_locking.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from concurrent.futures import ThreadPoolExecutor
2+
import numpy as np
3+
import os
4+
import tempfile
5+
import threading
6+
import time
7+
8+
import fitsio
9+
10+
11+
def test_locking_read():
12+
nt = 10
13+
rng = np.random.RandomState(seed=10)
14+
data = rng.normal(size=(100, 100))
15+
lock = threading.RLock()
16+
17+
def _read_file(fp):
18+
with lock:
19+
time.sleep(0.1)
20+
return fp[0].read()
21+
22+
with tempfile.TemporaryDirectory() as tmpdir:
23+
fname = os.path.join(tmpdir, "fname.fits")
24+
25+
with fitsio.FITS(fname, "rw", clobber=True) as fp:
26+
fp.write_image(data)
27+
28+
with fitsio.FITS(fname) as fp:
29+
t0 = time.time()
30+
with ThreadPoolExecutor(max_workers=nt) as exc:
31+
futs = [exc.submit(_read_file, fp) for _ in range(nt)]
32+
for fut in futs:
33+
res = fut.result()
34+
np.testing.assert_array_equal(res, data)
35+
t0 = time.time() - t0
36+
assert t0 > 1.0

0 commit comments

Comments
 (0)