preface
For multi process and multi thread applications, the correct synchronization and update of data cannot be achieved without lock and signal,swoole
The lock and signal in the system are basically adoptedpthread
A series of functions are implemented.UNIX
There are many types of locks in: mutex lock, spin lock, file lock, read-write lock, atomic lock, which will be explained in this sectionswoole
The definition and use of various locks in.
Learning notes of APUE — thread and lock
Learning notes of APUE — advanced IO and file lock
data structure
-
swoole
No matter what kind of lock, its data structure isswLock
This data structure has a federation insideobject
The combination can be mutex lock, spin lock, file lock, read-write lock, atomic lock,type
You can refer to the type of the lock. The specific options areSW_LOCKS
This enumeration type - The structure also defines several function pointers, which are similar to the interfaces that each lock needs to implement
lock_rd
andtrylock_rd
Two functions are designed specifically forswFileLock
andswRWLock
Designed, other locks do not have these two functions.
typedef struct _swLock
{
int type;
union
{
swMutex mutex;
#ifdef HAVE_RWLOCK
swRWLock rwlock;
#endif
#ifdef HAVE_SPINLOCK
swSpinLock spinlock;
#endif
swFileLock filelock;
swSem sem;
swAtomicLock atomlock;
} object;
int (*lock_rd)(struct _swLock *);
int (*lock)(struct _swLock *);
int (*unlock)(struct _swLock *);
int (*trylock_rd)(struct _swLock *);
int (*trylock)(struct _swLock *);
int (*free)(struct _swLock *);
} swLock;
enum SW_LOCKS
{
SW_RWLOCK = 1,
#define SW_RWLOCK SW_RWLOCK
SW_FILELOCK = 2,
#define SW_FILELOCK SW_FILELOCK
SW_MUTEX = 3,
#define SW_MUTEX SW_MUTEX
SW_SEM = 4,
#define SW_SEM SW_SEM
SW_SPINLOCK = 5,
#define SW_SPINLOCK SW_SPINLOCK
SW_ATOMLOCK = 6,
#define SW_ATOMLOCK SW_ATOMLOCK
};
mutex
Mutex is the most commonly used process / thread lock,swMutex
What’s the basis of thispthread_mutex
Therefore, the data structure has only two member variables:_lock
、attr
:
typedef struct _swMutex
{
pthread_mutex_t _lock;
pthread_mutexattr_t attr;
} swMutex;
Creation of mutex
The creation of mutex ispthread_mutex
Mutex initialization, first initialize the mutex propertiespthread_mutexattr_t attr
, set whether the mutex should be shared by the process, and then set each function about the lock:
int swMutex_create(swLock *lock, int use_in_process)
{
int ret;
bzero(lock, sizeof(swLock));
lock->type = SW_MUTEX;
pthread_mutexattr_init(&lock->object.mutex.attr);
if (use_in_process == 1)
{
pthread_mutexattr_setpshared(&lock->object.mutex.attr, PTHREAD_PROCESS_SHARED);
}
if ((ret = pthread_mutex_init(&lock->object.mutex._lock, &lock->object.mutex.attr)) < 0)
{
return SW_ERR;
}
lock->lock = swMutex_lock;
lock->unlock = swMutex_unlock;
lock->trylock = swMutex_trylock;
lock->free = swMutex_free;
return SW_OK;
}
Mutex function
The function of mutex is to call the correspondingpthread_mutex
Series function:
static int swMutex_lock(swLock *lock)
{
return pthread_mutex_lock(&lock->object.mutex._lock);
}
static int swMutex_unlock(swLock *lock)
{
return pthread_mutex_unlock(&lock->object.mutex._lock);
}
static int swMutex_trylock(swLock *lock)
{
return pthread_mutex_trylock(&lock->object.mutex._lock);
}
static int swMutex_free(swLock *lock)
{
pthread_mutexattr_destroy(&lock->object.mutex.attr);
return pthread_mutex_destroy(&lock->object.mutex._lock);
}
int swMutex_lockwait(swLock *lock, int timeout_msec)
{
struct timespec timeo;
timeo.tv_sec = timeout_msec / 1000;
timeo.tv_nsec = (timeout_msec - timeo.tv_sec * 1000) * 1000 * 1000;
return pthread_mutex_timedlock(&lock->object.mutex._lock, &timeo);
}
Read write lock
For the case of more read and less write, read-write lock can significantly improve the efficiency of the program,swRWLock
What’s the basis of thispthread_rwlock
Series function:
typedef struct _swRWLock
{
pthread_rwlock_t _lock;
pthread_rwlockattr_t attr;
} swRWLock;
Creation of read write lock
The process of creating a read-write lock is similar to that of creating a mutex
int swRWLock_create(swLock *lock, int use_in_process)
{
int ret;
bzero(lock, sizeof(swLock));
lock->type = SW_RWLOCK;
pthread_rwlockattr_init(&lock->object.rwlock.attr);
if (use_in_process == 1)
{
pthread_rwlockattr_setpshared(&lock->object.rwlock.attr, PTHREAD_PROCESS_SHARED);
}
if ((ret = pthread_rwlock_init(&lock->object.rwlock._lock, &lock->object.rwlock.attr)) < 0)
{
return SW_ERR;
}
lock->lock_rd = swRWLock_lock_rd;
lock->lock = swRWLock_lock_rw;
lock->unlock = swRWLock_unlock;
lock->trylock = swRWLock_trylock_rw;
lock->trylock_rd = swRWLock_trylock_rd;
lock->free = swRWLock_free;
return SW_OK;
}
Read write lock function
static int swRWLock_lock_rd(swLock *lock)
{
return pthread_rwlock_rdlock(&lock->object.rwlock._lock);
}
static int swRWLock_lock_rw(swLock *lock)
{
return pthread_rwlock_wrlock(&lock->object.rwlock._lock);
}
static int swRWLock_unlock(swLock *lock)
{
return pthread_rwlock_unlock(&lock->object.rwlock._lock);
}
static int swRWLock_trylock_rd(swLock *lock)
{
return pthread_rwlock_tryrdlock(&lock->object.rwlock._lock);
}
static int swRWLock_trylock_rw(swLock *lock)
{
return pthread_rwlock_trywrlock(&lock->object.rwlock._lock);
}
static int swRWLock_free(swLock *lock)
{
return pthread_rwlock_destroy(&lock->object.rwlock._lock);
}
File lock
File lock is a lock set for the scenario that multiple processes and threads write the same file at the same time. The underlying function isfcntl
:
typedef struct _swFileLock
{
struct flock lock_t;
int fd;
} swFileLock;
Creation of file lock
int swFileLock_create(swLock *lock, int fd)
{
bzero(lock, sizeof(swLock));
lock->type = SW_FILELOCK;
lock->object.filelock.fd = fd;
lock->lock_rd = swFileLock_lock_rd;
lock->lock = swFileLock_lock_rw;
lock->trylock_rd = swFileLock_trylock_rd;
lock->trylock = swFileLock_trylock_rw;
lock->unlock = swFileLock_unlock;
lock->free = swFileLock_free;
return 0;
}
File lock function
static int swFileLock_lock_rd(swLock *lock)
{
lock->object.filelock.lock_t.l_type = F_RDLCK;
return fcntl(lock->object.filelock.fd, F_SETLKW, &lock->object.filelock);
}
static int swFileLock_lock_rw(swLock *lock)
{
lock->object.filelock.lock_t.l_type = F_WRLCK;
return fcntl(lock->object.filelock.fd, F_SETLKW, &lock->object.filelock);
}
static int swFileLock_unlock(swLock *lock)
{
lock->object.filelock.lock_t.l_type = F_UNLCK;
return fcntl(lock->object.filelock.fd, F_SETLKW, &lock->object.filelock);
}
static int swFileLock_trylock_rw(swLock *lock)
{
lock->object.filelock.lock_t.l_type = F_WRLCK;
return fcntl(lock->object.filelock.fd, F_SETLK, &lock->object.filelock);
}
static int swFileLock_trylock_rd(swLock *lock)
{
lock->object.filelock.lock_t.l_type = F_RDLCK;
return fcntl(lock->object.filelock.fd, F_SETLK, &lock->object.filelock);
}
static int swFileLock_free(swLock *lock)
{
return close(lock->object.filelock.fd);
}
Spin lock
Spin lock is similar to mutex lock. The difference is that spin lock will not sink into the kernel when locking fails, but will idle. This kind of lock is more efficient, but will consume CPU
resources:
typedef struct _swSpinLock
{
pthread_spinlock_t lock_t;
} swSpinLock;
Creation of spin lock
int swSpinLock_create(swLock *lock, int use_in_process)
{
int ret;
bzero(lock, sizeof(swLock));
lock->type = SW_SPINLOCK;
if ((ret = pthread_spin_init(&lock->object.spinlock.lock_t, use_in_process)) < 0)
{
return -1;
}
lock->lock = swSpinLock_lock;
lock->unlock = swSpinLock_unlock;
lock->trylock = swSpinLock_trylock;
lock->free = swSpinLock_free;
return 0;
}
Spin lock function
static int swSpinLock_lock(swLock *lock)
{
return pthread_spin_lock(&lock->object.spinlock.lock_t);
}
static int swSpinLock_unlock(swLock *lock)
{
return pthread_spin_unlock(&lock->object.spinlock.lock_t);
}
static int swSpinLock_trylock(swLock *lock)
{
return pthread_spin_trylock(&lock->object.spinlock.lock_t);
}
static int swSpinLock_free(swLock *lock)
{
return pthread_spin_destroy(&lock->object.spinlock.lock_t);
}
Atomic lock
Different from the above locks,swoole
The atomic lock is notpthread
Series of locks, but custom implementation.
typedef volatile uint32_t sw_atomic_uint32_t;
typedef sw_atomic_uint32_t sw_atomic_t;
typedef struct _swAtomicLock
{
sw_atomic_t lock_t;
uint32_t spin;
} swAtomicLock;
Creation of atomic lock
int swAtomicLock_create(swLock *lock, int spin)
{
bzero(lock, sizeof(swLock));
lock->type = SW_ATOMLOCK;
lock->object.atomlock.spin = spin;
lock->lock = swAtomicLock_lock;
lock->unlock = swAtomicLock_unlock;
lock->trylock = swAtomicLock_trylock;
return SW_OK;
}
Locking of atomic lock
static int swAtomicLock_lock(swLock *lock)
{
sw_spinlock(&lock->object.atomlock.lock_t);
return SW_OK;
}
Locking logic function of atomic locksw_spinlock
It is very complicated. The specific steps are as follows:
- If the atomic lock is not locked, the atomic function is called
sw_atomic_cmp_set
(__sync_bool_compare_and_swap
)Lock it - If the atomic lock has been locked, if it is a single core, then call
sched_yield
Function gives up the execution right, because it means that the spin lock has been locked by other processes, but it has been forced to sleep. We need to give up the control right to the only onecpu
Run that process down, and note that you can’t choose at this time, otherwise it will be a deadlock. - If it’s multi-core, it’s necessary to keep idling to try to lock to prevent sleep. The interval between attempts to lock will increase exponentially, such as the first one clock cycle, the second two clock cycles, and the third four clock cycles
- Functions executed at intervals
sw_atomic_cpu_pause
Using the embedded assembly code, the purpose is to make thecpu
Idling prevents threads or processes from being occupied by other threads, causing sleep and wasting time in context recovery. - If it exceeds
SW_SPINLOCK_LOOP_N
Number of times, if the lock has not been obtained, you should also give up control. At this time, it is likely that the code protected by the lock will block
#define sw_atomic_cmp_set(lock, old, set) __sync_bool_compare_and_swap(lock, old, set)
#define sw_atomic_cpu_pause() __asm__ __volatile__ ("pause")
#define swYield() sched_yield() //or usleep(1)
static sw_inline void sw_spinlock(sw_atomic_t *lock)
{
uint32_t i, n;
while (1)
{
if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1))
{
return;
}
if (SW_CPU_NUM > 1)
{
for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1)
{
for (i = 0; i < n; i++)
{
sw_atomic_cpu_pause();
}
if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1))
{
return;
}
}
}
swYield();
}
}
Function of atomic lock
static int swAtomicLock_unlock(swLock *lock)
{
return lock->object.atomlock.lock_t = 0;
}
static int swAtomicLock_trylock(swLock *lock)
{
sw_atomic_t *atomic = &lock->object.atomlock.lock_t;
return (*(atomic) == 0 && sw_atomic_cmp_set(atomic, 0, 1));
}
Semaphore
Semaphore is also an important way of data synchronization
typedef struct _swSem
{
key_t key;
int semid;
} swSem;
Creation of semaphore
- Semaphore initialization needs to be called first
semget
Create a new semaphore -
semctl
Initializes the semaphore to 0
int swSem_create(swLock *lock, key_t key)
{
int ret;
lock->type = SW_SEM;
if ((ret = semget(key, 1, IPC_CREAT | 0666)) < 0)
{
return SW_ERR;
}
if (semctl(ret, 0, SETVAL, 1) == -1)
{
swWarn("semctl(SETVAL) failed");
return SW_ERR;
}
lock->object.sem.semid = ret;
lock->lock = swSem_lock;
lock->unlock = swSem_unlock;
lock->free = swSem_free;
return SW_OK;
}
V operation of semaphore
static int swSem_unlock(swLock *lock)
{
struct sembuf sem;
sem.sem_flg = SEM_UNDO;
sem.sem_num = 0;
sem.sem_op = 1;
return semop(lock->object.sem.semid, &sem, 1);
}
P operation of semaphore
static int swSem_lock(swLock *lock)
{
struct sembuf sem;
sem.sem_flg = SEM_UNDO;
sem.sem_num = 0;
sem.sem_op = -1;
return semop(lock->object.sem.semid, &sem, 1);
}
Destruction of semaphore
-
IPC_RMID
Used to destroy semaphores
static int swSem_free(swLock *lock)
{
return semctl(lock->object.sem.semid, 0, IPC_RMID);
}
Conditional variable
- Conditional variables are not used as
swLock
It’s a member of the United States - Conditional variables need more than
pthread_cond_t
, and the mutexswLock
typedef struct _swCond
{
swLock _lock;
pthread_cond_t _cond;
int (*wait)(struct _swCond *object);
int (*timewait)(struct _swCond *object, long, long);
int (*notify)(struct _swCond *object);
int (*broadcast)(struct _swCond *object);
void (*free)(struct _swCond *object);
int (*lock)(struct _swCond *object);
int (*unlock)(struct _swCond *object);
} swCond;
Creation of condition variable
int swCond_create(swCond *cond)
{
if (pthread_cond_init(&cond->_cond, NULL) < 0)
{
swWarn("pthread_cond_init fail. Error: %s [%d]", strerror(errno), errno);
return SW_ERR;
}
if (swMutex_create(&cond->_lock, 0) < 0)
{
return SW_ERR;
}
cond->notify = swCond_notify;
cond->broadcast = swCond_broadcast;
cond->timewait = swCond_timewait;
cond->wait = swCond_wait;
cond->lock = swCond_lock;
cond->unlock = swCond_unlock;
cond->free = swCond_free;
return SW_OK;
}
Functions of conditional variables
- It is worth noting that the use of conditional variables must be combined
swCond_lock
、swCond_unlock
Equifunction
static int swCond_notify(swCond *cond)
{
return pthread_cond_signal(&cond->_cond);
}
static int swCond_broadcast(swCond *cond)
{
return pthread_cond_broadcast(&cond->_cond);
}
static int swCond_timewait(swCond *cond, long sec, long nsec)
{
struct timespec timeo;
timeo.tv_sec = sec;
timeo.tv_nsec = nsec;
return pthread_cond_timedwait(&cond->_cond, &cond->_lock.object.mutex._lock, &timeo);
}
static int swCond_wait(swCond *cond)
{
return pthread_cond_wait(&cond->_cond, &cond->_lock.object.mutex._lock);
}
static int swCond_lock(swCond *cond)
{
return cond->_lock.lock(&cond->_lock);
}
static int swCond_unlock(swCond *cond)
{
return cond->_lock.unlock(&cond->_lock);
}
static void swCond_free(swCond *cond)
{
pthread_cond_destroy(&cond->_cond);
cond->_lock.free(&cond->_lock);
}