Threads share the same address space and code. However, for a variable in thread-local storage, each thread must have its own value; so the variable cannot be simply stored at a fixed address. There must be some indirection.
On 32-bit x86 CPU, with Linux, thread-local variables are accessed through the
%gs segment register, which was hitherto unused by application code. Segments are a remnant of older times; Linux has always defined all segments to start at address 0 and extend over four gigabytes, allowing code to forget them completely. However, they are still there, and one of them (
%gs) was revived to support thread-local storage. Indeed, all threads share the same memory, but each thread has its own set of register values.
The canary is a slot on the stack. Its purpose is to be overwritten when an overflow occurs, and the overflow is likely to impact the return address: the value which is written in the slot must be read back unchanged at the end of the function. That specific value is randomly chosen at the start of each thread, and each thread has its own; this means thread-local storage, so the canary value for a thread can be read by that thread at some fixed place relatively to
%gs, as known by that thread.