# enter_cs

Definition: | enter_cs(integer cs=0)
Description: | Begin mutually exclusive execution. Prevent other threads from executing similarly guarded code, or wait until they have completed.
pwa/p2js: | Not supported.
Comments: | If two (or more) threads update a shared variable simultaneously the result will not be as desired. While there are some atomic (lock) operations, you would certainly not want to run normal software using them where they are not needed, as they can easily be 30 times slower than the equivalent non-locked instructions. Modern pipelined processors offer huge performance benefits over those of a decade or more ago, but that means a read-update-write [such as i+=1, ie load i, add 1, store i, and you should understand that a single instruction such as add [i],1 actually gets decomposed into similar microinstructions by the cpu itself] gets spread out over more clock cycles. Without any locking, if one thread increments an integer at the same time as another decrements it, or more accurately the 14 or more clock cycles such instructions spend in their pipelines overlap, the result could easily be +/-1 the original value rather than the desired net effect of 0. If two or more threads were to simultaneously access the same sequence, when one or both were re-sizing/re-locating it, the outcome would be unpredictable to say the least. Using a critical section briefly pauses execution while any such conflicts exist, with minimal overhead. The critical section, if provided, should have previously been created using init_cs(). If the parameter is omitted, an internal one ("stdcs") is used. This is appropriate for things like the initialisation in printf(), but might cause excessive waiting or even deadlock in other cases, that is if you try to use the single internal default to lock two or more logically distinct things. Accidentally initialising and leaving a private critical section variable set to 0 would obviously have the same consequences. Care must be taken to avoid lock order inversion. When using multiple synchronization objects such as locks, it is vital to respect lock order. When acquiring more than one lock at a time, you must define an implicit precedence that is sometimes called a lock hierarchy or lock order. For example, if lock A is acquired before lock B somewhere in the code, and lock B is acquired before lock C elsewhere in the code, then the lock order is A, B, C and this order must always be followed throughout the code. Lock order inversion occurs when the locking order is not followed — for example, if lock B is acquired before lock A. Lock order inversion can cause deadlocks that are difficult to debug. To avoid such problems, all threads must acquire locks in the same order. Note that shared literal constants (such as "hello") may need to be referenced inside a critical section because of the shared hidden refcount updates, as per integer updates described above. See builtins\VM\pprntfN.e, routines Nan() and Inf(), for one possible thread-safe alternative. You may also want to consider a "deep_copy" (see builtins\VM\pDeleteN.e for one possible way) to create thread-specific instances of values that would not then require any subsequent locking. Mind you, calls to said "deep_copy" might need locking to avoid refcount issues, in the first place. Plus of course it makes no sense to store thread-specific instances of anything in a shared list. Note that deep_copy() does not produce thread-safe atoms and strings.
Example: |
    todo_cs = init_cs()
    ...
    -- main thread
    enter_cs(todo_cs)
    todo_list = append(todo_list,newtask)
    newtask = 0
    leave_cs(todo_cs)
    ...
    -- worker thread
    newtask = 0
    enter_cs(todo_cs)
    if length(todo_list)>0 then
        newtask = todo_list[1]
        todo_list = todo_list[2..$]
    end if
    leave_cs(todo_cs)
Note the use of newtask = 0 in the main thread; this avoids incidental refcount updates outside the critical section. In contrast the worker thread is the sole owner (only reference) to the newtask by the time it reaches the leave_cs(). You may also need similar lines in the main thread to remove other references to elements of newtask, which would be valid any time before the leave_cs() and indeed before the enter_cs(), if you were to prefer that.
Implementation: | via :%pEnterCS in builtins\VM\pHeap.e (an autoinclude).
See Also: | init_cs, delete_cs, try_cs, leave_cs builtins\VM\pfileioN.e uses an efficient low-level lock-free method to read fdtbl, search for "Multithreading issues".