Linux同步机制(一) - 线程锁
1 互斥锁
在线程实际运行过程中,我们经常需要多个线程保持同步。
这时可以用互斥锁来完成任务。互斥锁的使用过程中,主要有
pthread_mutex_init
pthread_mutex_destory
pthread_mutex_lock
pthread_mutex_unlock
这几个函数以完成锁的初始化,锁的销毁,上锁和释放锁操作。
1.1 锁的创建
锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
另外锁可以用pthread_mutex_init函数动态的创建,函数原型如下:
int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t * attr)
1.2 锁的属性
互斥锁属性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr)来初始化,然后可以调用其他的属性设置方法来设置其属性。
互斥锁的范围:可以指定是该进程与其他进程的同步还是同一进程内不同的线程之间的同步。可以设置为PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默认是后者,表示进程内使用锁。可以使用
int pthread_mutexattr_setpshared(pthread_mutexattr_t*mattr, int pshared)
pthread_mutexattr_getpshared(pthread_mutexattr_t*mattr,int *pshared)
用来设置与获取锁的范围;
互斥锁的类型:有以下几个取值空间:
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
可以用
pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)
获取或设置锁的类型。
1.3 锁的释放
调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
1.4 锁操作
对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个。
int pthread_mutex_lock(pthread_mutex_t*mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
1.5 代码讲解:
代码说明1:互斥锁基本应用
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int count = 0; void* consume(void *arg) { while(1) { pthread_mutex_lock(&mutex); printf("************************consume begin lock\n"); printf("************************consumed %d\n",count); count++; sleep(2); printf("************************consume over lock\n"); pthread_mutex_unlock(&mutex); printf("************************I'm out of pthread_mutex\n"); sleep(1); } return NULL; } void* produce( void * arg ) { while(1) { pthread_mutex_lock(&mutex ); printf("product begin lock\n"); printf("produced %d\n", count); printf("product over lock\n"); pthread_mutex_unlock(&mutex ); printf("I'm out of pthread_mutex\n"); sleep(1); } return NULL; } int main( void ) { pthread_t thread1,thread2; pthread_create(&thread1, NULL, &produce, NULL ); pthread_create(&thread2, NULL, &consume, NULL ); pthread_join(thread1,NULL); pthread_join(thread2,NULL); return 0; }
结果说明:
[root@rocket lock-free]# g++ -g -o pthread_mutex_lockpthread_mutex_lock.cpp -lpthread
[root@rocket lock-free]#./pthread_mutex_lock
product begin lock
produced 0
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 0
/*中间等待了2秒但是product线程没有执行!*/
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 1
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 2
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 2
************************consume overlock
************************I'm out ofpthread_mutex
代码说明2:pthread_mutext_trylock使用
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int count = 0; void* consume(void *arg) { while(1) { pthread_mutex_lock(&mutex); printf("************************consume begin lock\n"); printf("************************consumed %d\n",count); count++; sleep(2); printf("************************consume over lock\n"); pthread_mutex_unlock(&mutex); printf("************************I'm out of pthread_mutex\n"); sleep(1); } return NULL; } void* produce( void * arg ) { while(1) { if(pthread_mutex_trylock(&mutex ) == 0) { printf("product begin lock\n"); printf("produced %d\n", count ); printf("product over lock\n"); pthread_mutex_unlock(&mutex); printf("I'm out of pthread_mutex\n"); sleep(1); } else { printf("I have try!But i can`t lock the mutex!\n"); sleep(1); } } return NULL; } int main( void ) { pthread_t thread1,thread2; pthread_create(&thread1, NULL, &produce, NULL ); pthread_create(&thread2, NULL, &consume, NULL ); pthread_join(thread1,NULL); pthread_join(thread2,NULL); return 0; }
结果说明:
[root@rocket lock-free]# g++ -g -o pthread_mutex_trylock pthread_mutex_trylock.cpp -lpthread
[root@rocket lock-free]#./pthread_mutex_trylock
************************consume beginlock
************************consumed 0
/* trylock没有成功马上返回! */
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 1
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 2
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 2
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
2 读写锁
读写锁是因为有3种状态,所以可以有更高的并行性。
2.1 特性
一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁,正是因为这个特性,当读写锁是写加锁状态时,在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞。
当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须阻塞直到所有的线程释放锁。
通常,当读写锁处于读模式锁住状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞。
2.2 适用性
读写锁适合于对数据结构的读次数比写次数多得多的情况。因为,读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁。
2.3 API初始化和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功则返回0,出错则返回错误编号
同互斥锁一样,在释放读写锁占用的内存之前,需要先通过pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源。
2.4 读和写
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
这3个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作
同样,非阻塞的函数为:
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
非阻塞的获取锁操作, 如果可以获取则返回0,否则返回错误的EBUSY
2.5 代码讲解
代码说明1:读写锁基本应用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h> static pthread_rwlock_t rwlock; //读写锁对象 int count = 0; void *thread_function_read(void *arg) { while(1) { pthread_rwlock_rdlock(&rwlock); printf("************************%d, read count %d\n", pthread_self(), count); sleep(1); pthread_rwlock_unlock(&rwlock); usleep(100); } return NULL; } void *thread_function_write(void *arg) { while(1) { pthread_rwlock_wrlock(&rwlock); count++; printf("************************%d, write count %d\n", pthread_self(), count); sleep(5); pthread_rwlock_unlock(&rwlock); usleep(100); } return NULL; } int main(int argc, char *argv[]) { pthread_t rpthread1, rpthread2, wpthread; pthread_rwlock_init(&rwlock,NULL); pthread_create(&rpthread1, NULL, thread_function_read, NULL); pthread_create(&rpthread2, NULL, thread_function_read, NULL); pthread_create(&wpthread, NULL, thread_function_write, NULL); pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }
结果说明:
[root@rocket lock-free]#./pthread_rwlock
/* 2个读线程互相不阻塞 */
************************1442944768,read count 0
************************1432454912,read count 0
/* 写线程阻塞所有其它线程 */
************************1421965056,write count 1
************************1442944768,read count 1
************************1432454912,read count 1
************************1421965056,write count 2
************************1442944768,read count 2
************************1432454912,read count 2
************************1421965056,write count 3
************************1442944768,read count 3
************************1432454912,read count 3
************************1421965056,write count 4
有意思的是,加入去掉上面代码中thread_function_read和thread_function_write中的usleep(100),则会出现以下结果
[root@rocket lock-free]#./pthread_rwlock
************************-1896831232,read count 0
************************-1907321088,read count 0
************************-1907321088,read count 0
************************-1896831232,read count 0
************************-1907321088,read count 0
************************-1896831232,read count 0
************************-1907321088,read count 0
发现抢不到写锁,按我原先的理解,因为reader线程先启动,所以首先是reader抢到锁,reader抢到锁以后,writer阻塞在锁请求上,当reader释放以后,应该轮到writer才对啊,可是不是这样的!当reader释放后再次请求锁时,还是能拿到!writer基本抢不到锁!
查手册写到,"The pthread_rwlock_rdlock() function applies a read lock tothe read-write lock referenced by rwlock. The calling thread acquires the readlock if a writer does not hold the lock and there are no writers blocked on thelock. It is unspecified whether the calling thread acquires the lock when awriter does not hold the lock and there are writers waiting for the lock" 意思就是说,没有writer在等写锁的时辰,reader是可以拿到读锁的。然则没有划定,若是有writer在期待写锁,该若何?
还好,Linux有pthread_rwlockattr_setkind_np这个函数。
enum
{
PTHREAD_RWLOCK_PREFER_READER_NP,
PTHREAD_RWLOCK_PREFER_WRITER_NP,
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
PTHREAD_RWLOCK_DEFAULT_NP =PTHREAD_RWLOCK_PREFER_READER_NP
};
可是直接pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NP);
没用!为啥呢?连man页都没有,所以我思疑这个函数没实现,因而就用debuginfo-install glibc? 装glibc的调试符号,然后用gdb跟进去,发现pthread_rwlockattr_setkind_np确切是有实现的,代码很简单,更改了attr的一个成员变量。那是为啥呢?
再谷歌,终究找到了pthread_rwlockattr_setkind_np的man page,末尾有一段notes,让我年夜汗:
“Setting the value read-write lockkind to PTHREAD_RWLOCK_PREFER_WRITER_NP, results in the same behavior assetting the value to PTHREAD_RWLOCK_PREFER_READER_NP. As long as a readerthread holds the lock the thread holding a write lock will be starved. Settingthe kind value to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, allows thewriter to run. However, the writer may not be recursive as is implied by thename. “
意思就是说,
PTHREAD_RWLOCK_PREFER_WRITER_NP和PTHREAD_RWLOCK_PREFER_READER_NP是一样滴!应当设置成PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP才对!可是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP也是名存实亡滴,它才不会recursive 呢。
这样就有了代码说明2:读写锁优先级的使用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h> static pthread_rwlock_t rwlock; //读写锁对象 int count = 0; void *thread_function_read(void *arg) { while(1) { pthread_rwlock_rdlock(&rwlock); printf("************************%d, read count %d\n", pthread_self(), count); sleep(1); pthread_rwlock_unlock(&rwlock); //usleep(100); } return NULL; } void *thread_function_write(void *arg) { while(1) { pthread_rwlock_wrlock(&rwlock); count++; printf("************************%d, write count %d\n", pthread_self(), count); sleep(1); pthread_rwlock_unlock(&rwlock); usleep(100); } return NULL; } int main(int argc, char *argv[]) { pthread_t rpthread1, rpthread2, wpthread; pthread_rwlockattr_t attr; pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); pthread_rwlock_init(&rwlock, &attr); pthread_create(&rpthread1, NULL, thread_function_read, NULL); pthread_create(&rpthread2, NULL, thread_function_read, NULL); pthread_create(&wpthread, NULL, thread_function_write, NULL); pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }
运行结果:
[root@rocket lock-free]#./pthread_rwlock_withpriority
************************1529054976,read count 0
************************1518565120,read count 0
************************1508075264,write count 1
************************1529054976,read count 1
************************1518565120,read count 1
************************1508075264,write count 2
************************1529054976,read count 2
************************1518565120,read count 2
************************1508075264,write count 3
这样就不会导致writer饿死。
代码说明3:pthread_rwlock_tryrdlock使用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h> static pthread_rwlock_t rwlock; //读写锁对象 int count = 0; void *thread_function_read(void *arg) { int print_count = 0; while(1) { if (pthread_rwlock_tryrdlock(&rwlock) == 0) { printf("************************%d, read count %d\n", pthread_self(), count); sleep(1); pthread_rwlock_unlock(&rwlock); usleep(100); } else { print_count++; if (print_count % 10 == 0) { printf("I have try!But i can`t lock the rdlock!\n"); print_count = 0; } usleep(100); } } return NULL; } void *thread_function_write(void *arg) { while(1) { pthread_rwlock_wrlock(&rwlock); count++; printf("************************%d, write count %d\n", pthread_self(), count); sleep(5); pthread_rwlock_unlock(&rwlock); usleep(100); } return NULL; } int main(int argc, char *argv[]) { pthread_t rpthread1, rpthread2, wpthread; pthread_rwlock_init(&rwlock,NULL); pthread_create(&rpthread1, NULL, thread_function_read, NULL); pthread_create(&rpthread2, NULL, thread_function_read, NULL); pthread_create(&wpthread, NULL, thread_function_write, NULL); pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }
结果说明:
************************1819674368,read count 0
************************1809184512,read count 0
************************1798694656,write count 1
/* trylock没有成功马上返回! */
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
************************1819674368,read count 1
************************1809184512,read count 1
************************1798694656, writecount 2
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
3 自旋锁
自旋锁是SMP架构中的一种low-level的同步机制。
当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。对于自旋锁需要注意:
由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。(在内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起)
Pthreads提供的与Spin Lock锁操作相关的API主要有:
intpthread_spin_destroy(pthread_spinlock_t *);
int pthread_spin_init(pthread_spinlock_t*, int);
intpthread_spin_lock(pthread_spinlock_t *);
intpthread_spin_trylock(pthread_spinlock_t *);
intpthread_spin_unlock(pthread_spinlock_t *);
3.1 初始化自旋锁
pthread_spin_init用来申请使用自旋锁所需要的资源并且将它初始化为非锁定状态。pshared的取值及其含义:
PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。
PTHREAD_PROCESS_PRIVATE:仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。
3.2 获得一个自旋锁
pthread_spin_lock用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的。
3.3 尝试获取一个自旋锁
pthread_spin_trylock会尝试获取指定的自旋锁,如果无法获取则理解返回失败。
3.4 释放(解锁)一个自旋锁
pthread_spin_unlock用于释放指定的自旋锁。
3.5 销毁一个自旋锁
pthread_spin_destroy用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)在调用该函数之后如果没有调用pthread_spin_init重新初始化自旋锁,则任何尝试使用该锁的调用的结果都是未定义的。如果调用该函数时自旋锁正在被使用或者自旋锁未被初始化则结果是未定义的。
4 特性对比
锁类型 |
锁特性 |
适用场景 |
互斥锁mutex |
会导致线程切换 |
一般情况下的首选 |
读写锁rwlock |
同一时间只能有一个writer 可以同时有多个reader |
读多写少的场景 |
自旋锁spinlock |
不会导致线程切换 会导致CPU利用率升高 适合小代码段 |
小代码段,加锁不是很频繁的场景 |
版权声明:本文为博主原创文章,未经博主允许不得转载。