CANlib supports programs with multiple threads as long as one important condition is met: A handle to a CAN circuit should be used in only one thread.
This means that a handle obtained in one thread by a call to canOpenChannel() may not be used in any other thread. Each thread has to open its own handle to the circuit.
Also note that you must call canBusOn and canBusOff once for each handle even if the handles are opened on the same physical channel.
If you are using the same channel via multiple handles, note that the default behaviour is that the different handles will "hear" each other just as if each handle referred to a channel of its own. If you open, say, channel 0 from thread A and thread B and then send a message from thread A, it will be "received" by thread B. This behaviour can be changed using canIOCTL_SET_LOCAL_TXECHO.
Init access means that the thread that owns the handle can set bit rate and CAN driver mode. Init access is the default. At most one thread can have init access to any given channel. If you try to set the bit rate or CAN driver mode for a handle to which you don't have init access, the call will silently fail (i.e. canOK is returned although the call had no effect), unless you enable access error reporting by calling canIoCtl(). Access error reporting is by default off.
In spite of what was said above, you can use a single handle in different threads, provided you create the appropriate mutual exclusion mechanisms yourself. Two threads should never call CANlib simultaneously unless they are using different handles. You should also refrain from using calls to CANlib APIs that block (e.g. canReadWait()) if you are inside a critical region.