1 | # Building the library |
---|
2 | |
---|
3 | Run: |
---|
4 | |
---|
5 | ./configure && make && make install |
---|
6 | |
---|
7 | Use: |
---|
8 | |
---|
9 | ./configure --singular=/path/to/singular |
---|
10 | |
---|
11 | instead if you want to use this with a different Singular installation. |
---|
12 | |
---|
13 | The "make install" command will install `systhreads.so` and `systhreads.lib` |
---|
14 | in the MOD and LIB directories. |
---|
15 | |
---|
16 | # Usage |
---|
17 | |
---|
18 | The library can be used by loading it with: |
---|
19 | |
---|
20 | LIB "systhreads.lib"; |
---|
21 | |
---|
22 | # Threads |
---|
23 | |
---|
24 | Threads can be created with `createThread()`. Once no longer in use, |
---|
25 | thread resources should be disposed of with `joinThread()`. It is |
---|
26 | possible to evaluate expressions in an interpreter with `threadEval()` |
---|
27 | and retrieve the result with `threadResult()`. |
---|
28 | |
---|
29 | Example: |
---|
30 | |
---|
31 | thread t = createThread(); |
---|
32 | threadEval(t, quote("foo" + "bar")); |
---|
33 | threadResult(t); |
---|
34 | joinThread(t); |
---|
35 | |
---|
36 | If no result is needed, `threadExec(th, expr)` can be used instead, as |
---|
37 | can be the variants `threadExecString(th, string)` and |
---|
38 | `threadExecFile(th, filename)`. |
---|
39 | |
---|
40 | To load a library into a thread, `threadLoadLib(th, libname)` is available. |
---|
41 | |
---|
42 | A unique numeric per-thread id is available via `threadID()`. To check |
---|
43 | if the program is executing the main thread, the `mainThread()` function |
---|
44 | can be used; it'll return `1` for the main thread and `0` for all other |
---|
45 | threads. |
---|
46 | |
---|
47 | # Shared objects |
---|
48 | |
---|
49 | This library primarily provides a number of types to share data |
---|
50 | between threads, such as channels or atomic hash tables. We call |
---|
51 | instances of these types "shared objects". |
---|
52 | |
---|
53 | Shared objects can contain basic Singular values, such as integers, |
---|
54 | strings, lists, numbers (note: not all types of numbers are currently |
---|
55 | supported), rings, polynomials, and ideals (note that not all Singular |
---|
56 | types are supported yet). They can also contain references to other |
---|
57 | shared objects. When basic singular values are stored in a shared |
---|
58 | object, a copy of that value is created (if necessary, along with any |
---|
59 | ring it is based on). Shared objects that are stored in other shared |
---|
60 | objects are stored as references and are not copied. |
---|
61 | |
---|
62 | # Channels |
---|
63 | |
---|
64 | Channels are created via `makeChannel(uri)`. You can use |
---|
65 | `sendChannel(ch, val)` to send values to a channel and |
---|
66 | `receiveChannel(ch)` to read them from the channel. |
---|
67 | |
---|
68 | channel ch = makeChannel("channel:example"); |
---|
69 | sendChannel(ch, 314); |
---|
70 | receiveChannel(ch); |
---|
71 | |
---|
72 | Also, `statChannel(ch)` returns the number of elements in the channel. |
---|
73 | Note that this is an estimate, as new elements can concurrently be |
---|
74 | written to or existing elements retrieved from the channel. |
---|
75 | |
---|
76 | # Syncronization Variables |
---|
77 | |
---|
78 | Synchronization Variables are created via `makeSyncVar(uri)`. You can |
---|
79 | use `writeSyncVar(sv, val)` to write a value to a synchronization |
---|
80 | variable and `readSyncVar(sv)` to read from it. Note that writing a |
---|
81 | synchronization variable more than once results in an error and reading |
---|
82 | from a synchronization variable that has not yet been written to will |
---|
83 | block the underlying thread. |
---|
84 | |
---|
85 | Example: |
---|
86 | |
---|
87 | syncvar sv = makeSyncVar("syncvar:example"); |
---|
88 | writeSyncVar(sv, list(1, 2, 3)); |
---|
89 | readSyncVar(sv); |
---|
90 | |
---|
91 | It is possible to modify the value of a syncvar after it has been |
---|
92 | initialized with `updateSyncVar(sv, procname, ...)`. This procedure |
---|
93 | updates a syncvar with the result of applying procname to its contents |
---|
94 | (and any additional arguments being passed in). |
---|
95 | |
---|
96 | Example: |
---|
97 | |
---|
98 | syncvar sv = makeSyncVar("syncvar:example"); |
---|
99 | writeSyncVar(sv, 0); |
---|
100 | proc add(int x, int y) { return (x+y); } |
---|
101 | updateSyncVar(sv, "add", 1); |
---|
102 | updateSyncVar(sv, "add", 2); |
---|
103 | |
---|
104 | The intended use of `updateSyncVar` is to have it store a sequence |
---|
105 | of monotonically increasing values (such as a sequence of integers |
---|
106 | or a sequence of sets where each set is a superset of its predecessor) |
---|
107 | until the value satisfies a condition. |
---|
108 | |
---|
109 | Like all shared objects, synchronization variables can contain |
---|
110 | other shared objects. This is particularly useful to make them |
---|
111 | globally available across all threads. |
---|
112 | |
---|
113 | Example: |
---|
114 | |
---|
115 | syncvar channel_var = makeSyncVar("syncvar:channel"); |
---|
116 | writeSyncVar(channel_var, makeChannel("channel:main")); |
---|
117 | channel ch = readSyncVar(channel_var); |
---|
118 | sendChannel(ch, "test"); |
---|
119 | readChannel(ch); |
---|
120 | |
---|
121 | # Atomic Tables |
---|
122 | |
---|
123 | Atomic hash tables exist to store values that can concurrently been |
---|
124 | accessed by multiple threads. An atomic table is created via |
---|
125 | `makeAtomicTable(uri)`; it can be written to with `putTable(table, key, |
---|
126 | value)`, read with `getTable(table, key)`, and you can check whether |
---|
127 | a table holds a key with `inTable(table, key)`. |
---|
128 | |
---|
129 | Example: |
---|
130 | |
---|
131 | atomic_table table = makeAtomicTable("table:example"); |
---|
132 | putTable(table, "foo", 314); |
---|
133 | inTable(table, "foo"); |
---|
134 | inTable(table, "bar"); |
---|
135 | getTable(table, "foo"); |
---|
136 | |
---|
137 | # Atomic Lists |
---|
138 | |
---|
139 | Atomic lists work like atomic hash tables, but are indexed by integers |
---|
140 | rather than strings. They are created via `makeAtomicList(uri)`, are |
---|
141 | written with `putList(list, index, value)`, and read with `getList(list, |
---|
142 | index)`. Atomic lists resize automatically if a value is entered at an |
---|
143 | index beyond the end of the list. |
---|
144 | |
---|
145 | Example: |
---|
146 | |
---|
147 | atomic_list list = makeAtomicList("list:example"); |
---|
148 | putList(list, 3, 2718) |
---|
149 | getList(list, 3); |
---|
150 | |
---|
151 | # Regions |
---|
152 | |
---|
153 | Regions are individually lockable regions of memory that can contain |
---|
154 | other objects, namely shared hash tables and lists. These tables and |
---|
155 | lists work like atomic tables and lists, but can only be accessed when |
---|
156 | the region is locked. |
---|
157 | |
---|
158 | Example: |
---|
159 | |
---|
160 | region r = makeRegion("region:example"); |
---|
161 | lockRegion(r); |
---|
162 | shared_table table = makeSharedTable(r, "table"); |
---|
163 | shared_list list = makeSharedList(r, "list"); |
---|
164 | putTable(table, "foo", 314); |
---|
165 | getTable(table, "foo"); |
---|
166 | putList(list, 1, 2718); |
---|
167 | getList(list, 1); |
---|
168 | unlockRegion(r); |
---|
169 | |
---|
170 | Attempting to use list and table operations while the region is not |
---|
171 | locked by the current thread will result in an error. |
---|
172 | |
---|
173 | # Region locks |
---|
174 | |
---|
175 | To make management of region locking easier, there is a regionlock |
---|
176 | type. This can store the result of a `lockRegion(r)` call and will |
---|
177 | automatically unlock when it goes out of scope. |
---|
178 | |
---|
179 | Example: |
---|
180 | |
---|
181 | proc testGlobals(region r, string key) { |
---|
182 | regionlock lk = regionLock(r); |
---|
183 | shared_table globals = makeSharedTable(r, "globals"); |
---|
184 | return(inTable(globals, key)); |
---|
185 | } |
---|