1ggLockCreate(3) GGI ggLockCreate(3)
2
3
4
6 ggLockCreate, ggLockDestroy, ggLock, ggUnlock, ggTryLock : Lowest com‐
7 mon denominator locking facilities
8
10 #include <ggi/gg.h>
11
12 void *ggLockCreate(void);
13
14 int ggLockDestroy(void *lock);
15
16 void ggLock(void *lock);
17
18 void ggUnlock(void *lock);
19
20 int ggTryLock(void *lock);
21
22
24 These functions allow sensitive resource protection to prevent simulta‐
25 neous or interleaved access to resources. For developers accustomed to
26 POSIX-like threading environments it is important to differentiate a
27 gglock from a "mutex". A gglock fills Bboth* the role of a "mutex" and
28 a "condition" (a.k.a. an "event" or "waitqueue") through a simplified
29 API, and as such there is no such thing as a gglock "owner". A LibGG
30 lock is just locked or unlocked, it does not matter by what or when as
31 long as the application takes care never to create a deadlock that
32 never gets broken.
33
34 The locking mechanisms are fully functional even in single-threaded,
35 uninterrupted-flow-of-control environments. They must still be used
36 as described below even in these environments; They are never reduced
37 to non-operations.
38
39 The locking mechanisms are threadsafe, and are also safe to call from
40 inside LibGG task handlers. However, they are not safe to use in a
41 thread that may be cancelled during their execution, and they are not
42 guaranteed to be safe to use in any special context other than a LibGG
43 task, such as a signal handler or asyncronous procedure call.
44
45 Though the LibGG API does provide ample functionality for threaded
46 environments, do note that LibGG does not itself define any sort of
47 threading support, and does not require or guarantee that threads are
48 available. As such, if the aim of an application developer is to
49 remain as portable as possible, they should keep in mind that when cod‐
50 ing for both environments, there are only two situations where locks
51 are appropriate to use. These two situations are described in the
52 examples below.
53
54 Cleanup handlers created with ggRegisterCleanup(3) should not call any
55 of these functions.
56
57 LibGG must be compiled with threading support if multiple threads that
58 call any of these functions are to be used in the program. When LibGG
59 is compiled with threading support, the ggLock, ggUnlock, and ggTryLock
60 functions are guaranteed memory barriers for the purpose of multipro‐
61 cessor data access synchronization. (When LibGG is not compiled with
62 threading support, it does not matter, since separate threads should
63 not be using these functions in the first place.)
64
65 ggLockCreate creates a new lock. The new lock is initially unlocked.
66
67 ggLockDestroy destroys a lock, and should only be called when lock is
68 unlocked, otherwise the results are undefined and probably undesirable.
69
70 ggLock will lock the lock and return immediately, but only if the lock
71 is unlocked. If the lock is locked, ggLock will not return until the
72 lock gets unlocked by a later call to ggUnlock. In either case lock
73 will be locked when ggLock returns. ggLock is "atomic," such that only
74 one waiting call to ggLock will return (or one call to ggTryLock will
75 return successfully) each time lock is unlocked. Order is Bnot* guar‐
76 anteed by LibGG -- if two calls to ggLock are made at different times
77 on the same lock, either one may return when the lock is unlocked
78 regardless of which call was made first. (It is even possible for a
79 call to ggTryLock to grab the lock right after it is unlocked, even
80 though a call to ggLock was already waiting on the lock.)
81
82 ggTryLock attempts to lock the lock, but unlike ggLock it always
83 returns immediately whether or not the lock was locked to begin with.
84 The return value indicates whether the lock was locked at the time
85 ggTryLock was invoked. In either case lock will be locked when ggTry‐
86 Lock returns.
87
88 ggUnlock unlocks the lock. If any calls to ggLock or ggTryLock are
89 subsequently invoked, or have previously been invoked on the lock, one
90 of the calls will lock lock and return. As noted above, which ggLock
91 call returns is not specified by LibGG and any observed behavior should
92 not be relied upon. Immediacy is also Bnot* guaranteed; a waiting call
93 to ggLock may take some time to return. ggUnlock may be called, suc‐
94 cessfully, even if lock is already unlocked, in which case, nothing
95 will happen (other than a memory barrier.)
96
97 In all the above functions, where required, the lock parameter Bmust*
98 be a valid lock, or the results are undefined, may contradict what is
99 written here, and, in general, bad and unexpected things might happen
100 to you and your entire extended family. The functions do Bnot* vali‐
101 date the lock; It is the responsibility of the calling code to ensure
102 it is valid before it is used.
103
104 Remember, locking is a complicated issue (at least, when coding for
105 multiple environments) and should be a last resort.
106
108 ggLockCreate returns a non-NULL opaque pointer to a mutex, hiding its
109 internal implementation. On failure, ggLockCreate returns NULL.
110
111 ggTryLock returns GGI_OK if the lock was unlocked, or GGI_EBUSY if the
112 lock was already locked.
113
114 ggLockDestroy returns GGI_OK on success or GGI_EBUSY if the lock is
115 locked.
116
118 One use of gglocks is to protect a critical section, for example access
119 to a global variable, such that the critical section is never entered
120 by more than one thread when a function is called in a multi-threaded
121 environment. It is important for developers working in a single-
122 threaded environment to consider the needs of multi-threaded environ‐
123 ments when they provide a function for use by others.
124
125 static int foo = 0;
126 static gglock *l;
127
128 void increment_foo(void) {
129 ggLock(l);
130 foo++;
131 ggUnlock(l);
132 }
133
134 In the above example, it is assumed that gglock is initialized using
135 ggLockCreate before any calls to increment_foo are made. Also note
136 that in the above example, when writing for maximum portability, incre‐
137 ment_foo should not be called directly or indirectly by a task handler
138 which was registered via ggAddTask because a deadlock may result
139 (unless it is somehow known that increment_foo is not being executed by
140 any code outside the task handler.)
141
142 Another use of gglocks is to delay or skip execution of a task handler
143 registered with ggAddTask(3). It is important for developers working
144 in a multi-threaded environment to consider this when they use tasks,
145 because in single-threaded environments tasks interrupt the flow of
146 control and may in fact themselves be immune to interruption. As such
147 they cannot wait for a locked lock to become unlocked -- that would
148 create a deadlock.
149
150 static gglock *t, *l, *s;
151 int misscnt = 0;
152
153 void do_foo (void) {
154 ggLock(t); /* prevent reentry */
155 ggLock(l); /* keep task out */
156 do_something();
157 ggUnlock(l); /* task OK to run again */
158 if (!ggTryLock(s)) { /* run task if it was missed */
159 if (misscnt) while (misscnt--) do_something_else();
160 ggUnlock(s);
161 }
162 ggUnlock(t); /* end of critical section */
163 }
164
165 /* This is called at intervals by the LibGG scheduler */
166 static int task_handler(struct gg_task *task) {
167 int do_one;
168
169 /* We know the main application never locks s and l at the
170 * same time. We also know it never locks either of the
171 * two more than once (e.g. from more than one thread.)
172 */
173
174 if (!ggTryLock(s)) {
175 /* Tell the main application to run our code for us
176 * in case we get locked out and cannot run it ourselves.
177 */
178 misscnt++;
179 ggUnlock(s);
180 if (ggTryLock(l)) return; /* We got locked out. */
181 } else {
182 /* The main application is currently running old missed
183 * tasks. But it is using misscnt, so we can't just ask
184 * it to do one more.
185 *
186 * If this is a threaded environment, we may spin here for
187 * while in the rare case that the main application
188 * unlocked s and locked l between the above ggTryLock(s)
189 * and the below ggLock(l). However we will get control
190 * back eventually.
191 *
192 * In a non-threaded environment, the below ggLock cannot
193 * wedge, because the main application is stuck inside the
194 * section where s is locked, so we know l is unlocked.
195 */
196 ggLock(l);
197 do_something_else();
198 ggUnlock(l);
199 return;
200 }
201
202 /* now we know it is safe to run do_something_else() as
203 * do_something() cannot be run until we unlock l.
204 * However, in threaded environments, the main application may
205 * have just started running do_something_else() for us already.
206 * If so, we are done, since we already incremented misscnt.
207 * Otherwise we must run it ourselves, and decrement misscnt
208 * so it won't get run an extra time when we unlock s.
209 */
210 if (ggTryLock(s)) return;
211 if (misscnt) while (misscnt--) do_something_else();
212 ggUnlock(s);
213 ggUnlock(l);
214 }
215
216 In the above example, the lock t prevents reentry into the dofoo sub‐
217 routine the same as the last example. The lock l prevents do_some‐
218 thing_else() from being called while do_something() is running. The
219 lock s is being used to protect the misscnt variable and also acts as a
220 memory barrier to guarantee that the value seen in misscnt is up-to-
221 date. The code in function dofoo will run do_something_else() after
222 do_something() if the task happened while do_something() was running.
223 The above code will work in multi-threaded-single-processor, multi-
224 threaded-multi-processor, and single-threaded environments.
225 Note: The above code assumes do_something_else() is reentrant.
226
228 pthread_mutex_init(3)
229
230
231
232libgg-1.0.x 2005-08-26 ggLockCreate(3)