One technique is "fixed-timer-length". We maintain separate timer lists, all of them include elements of the same time to fire. That allows *appending* new events to the list as opposed to inserting them by time, which is costly due to searching time spent in a mutex. The performance benefit is noticeable. The limitation is you need a new timer list for each new timer length.
Another technique is the timer process slices off expired elements from the list in a mutex, but executes the timer after the mutex is left. That saves time greatly as whichever process wants to add/remove a timer, it does not have to wait until the current list is processed. However, be aware the timers may hit in a delayed manner; you have no guarantee in your process that after resetting a timer, it will no more hit. It might have been removed by timer process, and is waiting to be executed. The following example shows it:
PROCESS1 TIMER PROCESS 0. timer hits, it is removed from queue and about to be executed 1. process1 decides to reset the timer 2. timer is executed now 3. if the process1 naively thinks the timer could not have been executed after resetting the timer, it is WRONG -- it was (step 2.)
Example when it does not hurt:
P1 TIMER 0. RETR timer removed from list and scheduled for execution 1. 200/BYE received-> reset RETR, put_on_wait 2. RETR timer executed -- too late but it does not hurt 3. WAIT handler executed
The rule of thumb is don't touch data you put under a timer. Create data, put them under a timer, and let them live until they are safely destroyed from wait/delete timer. The only safe place to manipulate the data is from timer process in which delayed timers cannot hit (all timers are processed sequentially).
A "bad example" -- rewriting content of retransmission buffer in an unprotected way is bad because a delayed retransmission timer might hit. Thats why our reply retransmission procedure is enclosed in a REPLY_LOCK.
1.5.6