pthread locks implement memory barriers that will ensure that cache effects are made visible to other threads. You don’t need volatile to properly deal with the shared variable i
if the accesses to the shared variable are protected by pthread mutexes.
from http://www.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11:
The following functions synchronize
memory with respect to other threads:fork() pthread_barrier_wait() pthread_cond_broadcast() pthread_cond_signal() pthread_cond_timedwait() pthread_cond_wait() pthread_create() pthread_join() pthread_mutex_lock() // <==== pthread_mutex_timedlock() pthread_mutex_trylock() pthread_mutex_unlock() // <==== pthread_spin_lock() pthread_spin_trylock() pthread_spin_unlock() pthread_rwlock_rdlock() pthread_rwlock_timedrdlock() pthread_rwlock_timedwrlock() pthread_rwlock_tryrdlock() pthread_rwlock_trywrlock() pthread_rwlock_unlock() pthread_rwlock_wrlock() sem_post() sem_timedwait() sem_trywait() sem_wait() semctl() semop() wait() waitpid()