├── python_c_extension ├── __init__.py ├── pyproject.toml ├── setup.py ├── extension_module.c └── c_extension_gil.py ├── python_multithreading ├── __init__.py ├── function_threads.py └── class_threads.py ├── slides ├── GIL.key └── GIL.pdf ├── tests └── test_c_extension_gil.py ├── cpp_multithreading └── c_threads.cpp ├── .github └── workflows │ └── test.yml ├── README.md └── .gitignore /python_c_extension/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python_multithreading/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /slides/GIL.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aktech/gil_talk/HEAD/slides/GIL.key -------------------------------------------------------------------------------- /slides/GIL.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aktech/gil_talk/HEAD/slides/GIL.pdf -------------------------------------------------------------------------------- /python_c_extension/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "gil_talk" 7 | version = "0.2" 8 | -------------------------------------------------------------------------------- /python_c_extension/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import Extension, setup 2 | 3 | setup( 4 | name='Extension Module', 5 | version="0.2", 6 | description='Python C Extension module', 7 | ext_modules=[ 8 | Extension( 9 | name="extension_module", 10 | sources=["python_c_extension/extension_module.c"], 11 | ), 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /python_multithreading/function_threads.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import time 4 | import threading 5 | 6 | TOTAL_WORK = 10000000 7 | 8 | 9 | def countdown(count): 10 | while count > 0: 11 | count -= 1 12 | 13 | 14 | # Single Thread 15 | start = time.time() 16 | countdown(TOTAL_WORK) 17 | print('Single Thread: %8f' % (time.time() - start)) 18 | 19 | # Two Threads 20 | thread1 = threading.Thread(target=countdown, args=(TOTAL_WORK / 2,)) 21 | thread2 = threading.Thread(target=countdown, args=(TOTAL_WORK / 2,)) 22 | 23 | start = time.time() 24 | thread1.start() 25 | thread2.start() 26 | thread1.join() 27 | thread2.join() 28 | print('Two Threads: %8f' % (time.time() - start)) 29 | -------------------------------------------------------------------------------- /python_multithreading/class_threads.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import time 4 | import threading 5 | 6 | TOTAL_WORK = 10000000 7 | 8 | 9 | class CountdownThread(threading.Thread): 10 | def __init__(self, count): 11 | threading.Thread.__init__(self) 12 | self.count = count 13 | 14 | def run(self): 15 | while self.count > 0: 16 | self.count -= 1 17 | return 18 | 19 | 20 | start = time.time() 21 | single_thread = CountdownThread(TOTAL_WORK // 2) 22 | single_thread.start() 23 | single_thread.join() 24 | end = time.time() 25 | print('Single Thread: ', end - start) 26 | 27 | 28 | start = time.time() 29 | 30 | # countdown(TOTAL_WORK) 31 | thread_1 = CountdownThread(TOTAL_WORK // 2) # Create the thread object 32 | thread_2 = CountdownThread(TOTAL_WORK // 2) # Create another thread 33 | 34 | thread_1.start() 35 | thread_2.start() 36 | thread_1.join() 37 | thread_2.join() 38 | 39 | end = time.time() 40 | print('Two Threads: ', end - start) 41 | -------------------------------------------------------------------------------- /tests/test_c_extension_gil.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from python_c_extension.extension_module import count 4 | from python_c_extension.c_extension_gil import run_count_in_single_thread 5 | from python_c_extension.c_extension_gil import run_count_in_multiple_threads 6 | 7 | 8 | class TestCExtensionCount(unittest.TestCase): 9 | 10 | def test_count_function(self): 11 | self.assertEqual(count(int(10e5)), 1) 12 | 13 | def test_run_count_in_single_thread(self): 14 | time_taken = run_count_in_single_thread() 15 | self.assertGreater(time_taken, 0) 16 | 17 | def test_run_count_in_multiple_threads(self): 18 | time_taken = run_count_in_multiple_threads() 19 | self.assertGreater(time_taken, 0) 20 | 21 | def test_count_multiple_thread_faster_than_single_thread(self): 22 | single_thread_time_taken = run_count_in_single_thread() 23 | multiple_thread_time_taken = run_count_in_multiple_threads() 24 | self.assertGreater(single_thread_time_taken, multiple_thread_time_taken) 25 | -------------------------------------------------------------------------------- /python_c_extension/extension_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | static PyObject* count(PyObject* self, PyObject* args) 5 | { 6 | volatile unsigned long long int value; 7 | /* parse the input, from python integer to c long long int */ 8 | if (!PyArg_ParseTuple(args, "L", &value)) 9 | return NULL; 10 | /* if the above function returns -1, an appropriate Python exception will 11 | * have been set, and the function simply returns NULL 12 | */ 13 | 14 | Py_BEGIN_ALLOW_THREADS 15 | while (value > 0) { 16 | // Simply Count 17 | value -= 1; 18 | } 19 | Py_END_ALLOW_THREADS 20 | 21 | return Py_BuildValue("i", 1); 22 | } 23 | 24 | /* define functions in module */ 25 | static PyMethodDef myModule[] = 26 | { 27 | {"count", count, METH_VARARGS, "Count Loop"} 28 | }; 29 | 30 | /* Defines module */ 31 | static struct PyModuleDef extension_module = { 32 | PyModuleDef_HEAD_INIT, 33 | "extension_module", 34 | NULL, 35 | -1, 36 | myModule 37 | }; 38 | 39 | /* Inititialise */ 40 | PyMODINIT_FUNC 41 | PyInit_extension_module(void) 42 | { 43 | return PyModule_Create(&extension_module); 44 | } 45 | -------------------------------------------------------------------------------- /cpp_multithreading/c_threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | void *task(void *n_) { 10 | long long int n = *((long long int*)n_); 11 | for (long long int i = 0; i <= n; i++) {} 12 | return 0; 13 | } 14 | 15 | 16 | int main (int argc, char ** argv) { 17 | long long int N = 100000000; 18 | long long int *arg = (long long int *)malloc(sizeof(*arg)); 19 | std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); 20 | *arg = N; 21 | task(arg); 22 | std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); 23 | cout << "Elapsed time with 1 thread: " << std::chrono::duration_cast(end - begin).count() / 1000.0 <(end - begin).count() / 1000.0 < 0: 18 | n -= 1 19 | 20 | 21 | def run_count_in_single_thread(count_func=count): 22 | t1 = time() 23 | count_func(N) 24 | time_taken = time() - t1 25 | return time_taken 26 | 27 | 28 | def run_count_in_multiple_threads(count_func=count): 29 | t1 = time() 30 | th1 = Thread(target=count_func, args=(N // 2,)) 31 | th2 = Thread(target=count_func, args=(N // 2,)) 32 | th1.start(); th2.start() 33 | th1.join(); th2.join() 34 | time_taken = time() - t1 35 | return time_taken 36 | 37 | 38 | if __name__ == '__main__': 39 | print('Single Thread (C extension): %8f (s)' % (run_count_in_single_thread(count))) 40 | print('Two Threads (C extension): %8f (s)' % (run_count_in_multiple_threads(count))) 41 | print("="*100) 42 | print('Single Thread (Pure Python): %8f (s)' % (run_count_in_single_thread(count_pure_python))) 43 | print('Two Threads (Pure Python): %8f (s)' % (run_count_in_multiple_threads(count_pure_python))) 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: GIL Talk Demo 2 | 3 | on: push 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | version: ["3.9", "3.10", "3.11", "3.12"] 13 | runs-on: ubuntu-latest 14 | defaults: 15 | run: 16 | shell: bash -el {0} 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up Python 21 | if: matrix.version != 3.13 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: "${{ matrix.version }}" 25 | allow-prereleases: true 26 | 27 | - name: Setup Python via Miniconda 28 | if: matrix.version == 3.13 29 | uses: conda-incubator/setup-miniconda@v3.0.4 30 | with: 31 | python-version: 3.13 32 | channels: defaults,ad-testing/label/py313_nogil 33 | 34 | - name: Check Python version 35 | run: | 36 | python -VV 37 | 38 | - name: Check Python GIL enabled 39 | if: matrix.version == 3.13 40 | run: python -c "import sys; print(sys._is_gil_enabled())" 41 | 42 | - name: Install dependencies 43 | run: | 44 | pip install setuptools 45 | 46 | - name: Build C extension 47 | run: | 48 | python python_c_extension/setup.py build 49 | cp -r build/*/* python_c_extension 50 | 51 | - name: Run script in single and multi-thread mode 52 | run: | 53 | python python_c_extension/c_extension_gil.py 54 | env: 55 | PYTHONPATH: "." 56 | PYTHON_GIL: 0 57 | 58 | - name: Run Tests 59 | run: python -m unittest discover tests/ -vvv 60 | env: 61 | PYTHON_GIL: 0 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GIL Talk 2 | 3 | [![GIL Talk Demo](https://github.com/aktech/gil_talk/actions/workflows/test.yml/badge.svg)](https://github.com/aktech/gil_talk/actions/workflows/test.yml) 4 | 5 | A talk on Global Interpreter lock. 6 | 7 | ## Contents 8 | 9 | ### Functions threads API 10 | 11 | ```bash 12 | $ python python_multithreading/function_threads.py 13 | ``` 14 | 15 | ### Class based threads API 16 | 17 | ```bash 18 | $ python python_multithreading/class_threads.py 19 | ``` 20 | 21 | ### Extension Module 22 | 23 | Python C extension module is written in `extension_module.c` to build it on mac, run: 24 | 25 | ```bash 26 | $ python python_c_extension/setup.py build 27 | ``` 28 | 29 | The above will will compile `extension_module.c`, and produce an extension module 30 | in the build directory. Depending on the system, the module file will end up in 31 | a subdirectory `build/lib.system.extension_module.so`. 32 | 33 | Copy the built `*.so` file in the current directory, for e.g.: 34 | 35 | ```bash 36 | $ cp -r build/*/* python_c_extension 37 | ``` 38 | 39 | The function defined in extensions module is used in `c_ext_gil_test.py`, run it 40 | as following: 41 | 42 | ```bash 43 | $ python python_c_extension/c_extension_gil.py 44 | Single Thread: 2.118302 45 | Two Threads: 1.348021 46 | ``` 47 | 48 | ### Compile and run C Threads 49 | 50 | * compile 51 | 52 | ```bash 53 | $ g++ cpp_multithreading/c_threads.cpp -o cpp_multithreading/c_threads 54 | ``` 55 | 56 | * run 57 | 58 | ```bash 59 | $ ./cpp_multithreading/c_threads 60 | ``` 61 | 62 | ### Acknowledgement 63 | 64 | Thanks to [Garvit](https://garvit.in/) and [Suhaib](https://suheb.in/), who played a vital role in the 65 | preparation of this presentation. 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # PyCharm config 7 | .idea 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | .static_storage/ 60 | .media/ 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | .env 90 | .venv 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | --------------------------------------------------------------------------------