summaryrefslogtreecommitdiff
path: root/THREADS
blob: f45b2b1652a75519c2c8c49fb6d4fe2113b57e14 (plain)
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
-*- mode: text; mode: auto-fill -*-

Requirements to get threading to work with pygtk2
=================================================

Python side
-----------
* Python has a global interpreter lock, so no two threads can execute
  code at the same time.

* In order to execute python code, you need to hold the interpreter
  lock, and have the correct PyThreadState object set
  (PyThreadState_Swap is used for this).  As a convenience, the
  following functions can be used:
    void PyEval_AcquireThread(PyThreadState *tstate);
    void PyEval_ReleaseThread(PyThreadState *tstate);

* When C functions are called from python, the global interpreter lock
  is held.  If the function blocks, then no python code can execute in
  other threads during this time.  Python provides two macros to allow
  threads to run while the function executes:
    Py_BEGIN_ALLOW_THREADS
    Py_END_ALLOW_THREADS
  between these two macro calls, the global interpreter lock is
  released, and the current thread state is set to NULL.  This means
  that in order to execute python code, AcquireThread must be called.

GLib side
---------
* The glib main loop function blocks, so we need to allow threads so
  that other threads don't hang while in the event loop.

* Destroy notifies and signal handlers may be called in response to
  some function call (such as GObject.set_data or GtkButton.clicked),
  or while the main loop is running.  In the first case, the global
  interpreter lock will be held, and a thread state will be acquired.
  In the second case, the global interpreter lock will not be held by
  us and there will be a NULL thread state.  Example:
---- Cut Here ----
  import gtk
  ...
  b = gtk.GtkButton("Click me")
  def f(button):
      ... some python code ...
  b.connect("clicked", f)
  b.clicked()  # the signal handler f is called with the interpreter
               # lock held
  ...
  gtk.main()   # if the button is clicked while in the main loop, the
               # interpreter lock may be held by someone else
---- Cut Here ----

* We need to have a valid thread state before executing one of these
  callbacks, or bad things happen.

* Ideally, the solution chosen should work at the GObject level,
  rather than GTK/GDK level.

* In gtk 1.2 based PyGTK, we used a bit of code from Paul Fisher to
  handle this problem.  It relied on the global GDK lock to serialise
  requests to unblock threading.  As the GObject functions do not rely
  on a global lock being held, we can't use this.  Here is the
  existing code:
---- Cut Here ----
/* The threading code has been enhanced to be a little better with multiple
 * threads accessing GTK+.  Here are some notes on the changes by
 * Paul Fisher:
 *
 * If threading is enabled, we create a recursive version of Python's
 * global interpreter mutex using TSD.  This scheme makes it possible,
 * although rather hackish, for any thread to make a call into PyGTK,
 * as long as the GDK lock is held (that is, Python code is wrapped
 * around a threads_{enter,leave} pair).
 *
 * A viable alternative would be to wrap each and every GTK call, at
 * the Python/C level, with Py_{BEGIN,END}_ALLOW_THREADS.  However,
 * given the nature of Python threading, this option is not
 * particularly appealing.
 */

static GStaticPrivate pythreadstate_key = G_STATIC_PRIVATE_INIT;
static GStaticPrivate counter_key = G_STATIC_PRIVATE_INIT;

/* The global Python lock will be grabbed by Python when entering a
 * Python/C function; thus, the initial lock count will always be one.
 */
#  define INITIAL_LOCK_COUNT 1
#  define PyGTK_BLOCK_THREADS                                              \
   {                                                                       \
     gint counter = GPOINTER_TO_INT(g_static_private_get(&counter_key));   \
     if (counter == -INITIAL_LOCK_COUNT) {                                 \
       PyThreadState *_save;                                               \
       _save = g_static_private_get(&pythreadstate_key);                   \
       Py_BLOCK_THREADS;                                                   \
     }                                                                     \
     counter++;                                                            \
     g_static_private_set(&counter_key, GINT_TO_POINTER(counter), NULL);   \
   }

#  define PyGTK_UNBLOCK_THREADS                                            \
   {                                                                       \
     gint counter = GPOINTER_TO_INT(g_static_private_get(&counter_key));   \
     counter--;                                                            \
     if (counter == -INITIAL_LOCK_COUNT) {                                 \
       PyThreadState *_save;                                               \
       Py_UNBLOCK_THREADS;                                                 \
       g_static_private_set(&pythreadstate_key, _save, NULL);              \
     }                                                                     \
     g_static_private_set(&counter_key, GINT_TO_POINTER(counter), NULL);   \
   }
---- Cut Here ----

* One possible solution is to wrap every single pygtk call in
  Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS calls.  This would
  probably be quite slow.

* Can Paul Fisher's code be modified to work without needing the GDK
  lock for serialisation?