1ggLockCreate(3)                       GGI                      ggLockCreate(3)
2
3
4

NAME

6       ggLockCreate,  ggLockDestroy, ggLock, ggUnlock, ggTryLock : Lowest com‐
7       mon denominator locking facilities
8

SYNOPSIS

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

DESCRIPTION

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

RETURN VALUE

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

EXAMPLES

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

SEE ALSO

228       pthread_mutex_init(3)
229
230
231
232libgg-1.0.x                       2005-08-26                   ggLockCreate(3)
Impressum