|
Definition:
|
integer tid = new_dict(sequence kd_pairs={},
integer pool_only=0)
-- or --
integer tid = new_dict(string dictname="")
-- or --
integer tid = new_dict(integer existing_tid)
-- or --
sequence tids = new_dicts(integer n)
|
|
Description:
|
Creates a new dictionary, so that clashes with other uses of {key,data} pairings can be avoided, and optionally
initialises it with a set of {key,data} pairs, or a diagnostic name, or makes a complete copy of some other
already existing dictionary, or makes n empty dictionaries.
|
|
pwa/p2js:
|
Supported.
|
|
Comments:
|
If kd_pairs is a string then it is treated as a name for the dictionary, which may be useful for diagnostic purposes,
and can be retrieved using dict_name().
Otherwise every element of kd_pairs, if present, should be a sequence of length 2, and said {key,data} gets passed to
setd(), along with the id of the newly created dictionary.
The returned tid is intended to be passed as the last parameter to setd(), getd(),
deld(), destroy_dict(), traverse_dict(),
and similar, taking care not to miss any.
For smaller applications, there is a default dictionary, 1, that can be used without ever calling new_dict(), however larger
applications and in particular third party components will obviously benefit from keeping their data private (by the simple
act of not sharing their tid1).
When you have no further use for it, an entire dictionary can be removed by invoking destroy_dict(tid), except for the
default dictionary, 1, as mentioned above, which is special and is the only dictionary that cannot be deleted - attempts
to do so merely empty it and leave it still available for use.
Dictionaries are not thread-safe, for details of that, or if you are curious about the mysterious pool_only
parameter, see the technicalia.
The copy_tid = new_dict(existing_tid) is fast, performing only a few top-level reference count increments.
Note however that it is only thread-safe if both copy_tid and existing_tid are only ever used by the same
thread, otherwise locking is required around each and every call to any routine from dict.e that is passed
either of those now-shared ids, and possibly around anything that uses the results from such calls (keys,
values, and ref counts of said). It is also not possible to make a copy of an existing dictionary and give
it a new diagnostic name, instead the copy just gets "".
|
|
Disclaimer:
|
1 Rogue code that wantonly writes garbage to a tid of,
say, 5, when it did not obtain that 5 from new_dict(), can obviously wreak havoc. Should it ever prove necessary
it would probably be fairly easy to change integer tid to {integer tid, string passkey} just to make it far harder to
accidentally replicate a tid.
|
|
Example 1:
|
integer tid = new_dict({{1,"one"},{2,"two"}})
|
|
Example 2:
|
-- multithreaded applications need to use locking (see Technicalia)
integer dictcs = init_cs(), tid
enter_cs(dictcs)
tid = new_dict("thread data")
leave_cs(dictcs)
|
|
See Also:
|
enter_cs,
destroy_dict
|
|
Technicalia
|
Multiple threads concurrently accessing dictionaries will cause corruption unless protected as shown in Example 2.
If your application is not multithreaded, or only one thread accesses dictionaries, then nothing that follows is of
any concern to you, and if I were you, I’d get out while I still can.
A single critical section protecting all dictionaries is normally sufficient, and in the vast majority of cases there
would be no point making life any more difficult for yourself, however per-dictionary locks are also perfectly viable,
and if only one thread has access to a particular dictionary, then only new_dict() and destroy_dict() may need locking
- but for either of the last two options (ie per-dict and create/destroy-only-locking), then a big enough pool must be
created at the get-go:
If pool_only is greater than 1, the routine adds the specified number of entries to the freelist and returns 0.
Such a call is of course expected to be made before any create_thread() or other
new_dict() calls, and certainly before any destroy_dict() calls - you should verify
the result actually is zero to guard against the latter.
Subsequent invocations of new_dict(pool_only:=1) [presumably from within an appropriate critical section] will yield 0
if the pool is exhausted. In that case, the program should unlock, sleep and retry a bit later. You can actually specify
a diagnostic name and pool_only of 1 (or 0), however passing a string plus pool_only>1 triggers a fatal error.
If in fact you are trying to shut down, but other threads have died or forgotten to destroy_dict(), it is not entirely
unreasonable (after a time) to offer a "create anyway" option - it is not actually very likely, let alone guaranteed
to fail - unless some other thread is beavering away making as many calls to builtins\dict.e routines as it can.
I only mention this because systems which are multithreaded and require many dictionaries are somewhat more
likely to have complex and critical shutdown requirements, and stand to lose much more data if terminated abruptly.
Tip: it costs very little to create a pool of say 500 dicts, and any such overhead would be completely and utterly
dwarfed by trying to create 500 threads. Conversely, limiting the pool size may keep memory useage spikes down and
otherwise help spread out the workload.
Alternatively you can create as many(/all) dictionaries as you need up-front, before creating any threads, and hence
not need any locking, and farm them out to the threads as you create them, in which case you have implicitly created
a large enough pool, assuming there won’t be any more new_dict() calls further down the line or occurring in
other unrelated threads. Likewise should each thread invoke destroy_dict() that would
still need locking, but if you wait for them all to finish, then you can delete them all in one go without any locking
- again assuming there are no other threads using dictionaries still running. Not that there’s any harm or it
isn’t simpler to lock around bulk deletion, but note that locking around bulk creation would be insufficient
if the pool wasn’t already large enough beforehand.
Let me reiterate to make all this as clear as possible:
- If the program is not multithreaded, or only one thread accesses dictionaries, no locking required, go away now.
- If multiple threads might access the same dictionary, locks are required around every call.
- In the latter case you can have one lock for all dictionaries or one lock per dictionary (see last two points).
- If a dictionary is only ever accessed by one thread, you can limit locking to new_dict/destroy_dict, but you
must work within a limited-size pool of dictionaries (or lock all calls).
- Likewise any use of per-dictionary locks mandates the use of a limited-size pool of dictionaries.
- The locks around new_dict/destroy_dict calls must always of course be system-wide, not per-dictionary.
|