POSIX互斥锁
一、什么是互斥锁
另一种在多线程程序中同步访问手段是使用互斥量。程序员给某个对象加上一把“锁”,每次只允许一个线程去访问它。如果想对代码关键部分的访问进行控制,你必须在进入这段代码之前锁定一把互斥量,在完成操作之后再打开它。
互斥量函数有
pthread_mutex_init 初始化一个互斥量
pthread_mutex_lock 给一个互斥量加锁
pthread_mutex_trylock 加锁,如果失败不阻塞
pthread_mutex_unlock 解锁
可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所以在该互斥锁上的阻塞线程都会变成可进行状态,第一个变成运行状态的线程可以对互斥量加锁,其他线程在次被阻塞,等待下次运行状态。
互斥量用pthread_mutex_t数据类型来表示,在使用互斥量以前,必须首先对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化,如果动态地分配互斥量,那么释放内存前需要调用pthread_mutex_destroy.
二、初始化/回收互斥锁
1.
名称:: |
pthread_mutexattr_init |
功能: |
初始化互斥锁。 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutex_t *attr); |
参数: |
mutex 互斥量 attr 互斥锁属性 |
返回值: |
若成功则返回0,否则返回错误编号。 |
mutex是我们要锁住的互斥量,attr是互斥锁的属性,可用相应的函数修改,我们在下章介绍,要用默认的属性初始化互斥量,只需把attr设置为NULL。
2.
名称:: |
pthread_mutex_destroy |
功能: |
释放对互斥变量分配的资源 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutex_destroy(pthread_mutex_t *mutex); |
参数: |
|
返回值: |
若成功则返回0,否则返回错误编号。 |
三、对互斥量加减锁
3.
名称:: |
pthread_mutex_lock/ pthread_mutex_trylock/ pthread_mutex_unlock |
功能: |
对互斥量加/减锁 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); |
参数: |
|
返回值: |
若成功则返回0,否则返回错误编号。 |
对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程阻塞直至互斥量解锁。对互斥量解锁,需要调用pthread_mutex_unlock.
如果线程不希望被阻塞,他可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,否则就会失败,不能锁住互斥量,而返回EBUSY。
下面试例子可以证明对互斥量加锁的必要性:
我们先来看不加锁的程序。
#inlcude <stdio.h> #include <pthread.h> #inlcude <stdio.h> #include <unistd.h>
viid *thread_function(void *arg);
int run_now=1; /*用run_now代表共享资源*/
int main() { int print_count1=0; /*用于控制循环*/ prhread_t a_thread;
if(pthread_create(&a_thread,NULL,thread_function,NULL)!=0) /*创建一个进程*/ { perror(“Thread createion failed”); exit(1); }
while(print_count1++<5) { if(run_now==1) /主线程:如果run_now为1就把它修改为2*/ { printf(“main thread is run\n”); run_now=2; } else { printf(“main thread is sleep\n”); sleep(1); } } pthread_join(a_thread,NULL); /*等待子线程结束*/ exit(0); }
void *thread_function(void *arg) { int print_count2=0;
while(print_count2++<5) { if(run_now==2) /子线程:如果run_now为1就把它修改为1*/ { printf(“function thread is run\n”); run_now=1; } else { printf(“function thread is sleep\n”); sleep(1); } } pthread_exit(NULL); }
|
运行上面程序的运行结果为:
function thread is sleep
main thread is run
main thread is sleep
main thread is sleep
function thread is run
function thread is sleep
main thread is run
main thread is sleep
function thread is run
function thread is sleep
我们可以看到main线程和function线程是交替运行的。它们都可以对run_now进行操作。
下面是加锁的程序。
#inlcude <stdio.h> #include <pthread.h> #inlcude <stdio.h>
viid *thread_function(void *arg);
int run_now=1; /*用run_now代表共享资源*/ pthread_mutex_t work_mutex; /*定义互斥量*/
int main() { int res; int print_count1=0; prhread_t a_thread;
if(pthread_mutex_init(&work_mutex,NULL)!=0) /*初始化互斥量*/ { perror(“Mutex init faied”); exit(1); }
if(pthread_create(&a_thread,NULL,thread_function,NULL)!=0) /*创建新线程*/ { perror(“Thread createion failed”); exit(1); }
if(pthread_mutex_lock(&work_mutex)!=0) /*对互斥量加锁*/ { preeor(“Lock failed”); exit(1); } else printf(“main lock\n”);
while(print_count1++<5) { if(run_now==1) /主线程:如果run_now为1就把它修改为2*/ { printf(“main thread is run\n”); run_now=2; } else { printf(“main thread is sleep\n”); sleep(1); } }
if(pthread_mutex_unlock(&work_mutex)!=0) /*对互斥量解锁*/ { preeor(“unlock failed”); exit(1); } else printf(“main unlock\n”);
pthread_mutex_destroy(&work_mutex); /*收回互斥量资源*/ pthread_join(a_thread,NULL); /*等待子线程结束*/ exit(0); }
void *thread_function(void *arg) { int print_count2=0;
sleep(1); if(pthread_mutex_lock(&work_mutex)!=0) { perror(“Lock failed”); exit(1); } else printf(“function lock\n”); while(print_count2++<5) { if(run_now==2) /分进程:如果run_now为1就把它修改为1*/ { printf(“function thread is run\n”); run_now=1; } else { printf(“function thread is sleep\n”); sleep(1); } }
if(pthread_mutex_unlock(&work_mutex)!=0) /*对互斥量解锁*/ { perror(“unlock failed”); exit(1); } else printf(“function unlock\n”); pthread_exit(NULL); }
|
下面是运行结果:
main lock
main thread is run
main thread is sleep
main thread is sleep
main thread is sleep
main thread is sleep
main unlock
function lock
function thread is run
function thread is sleep
function thread is sleep
function thread is sleep
function thread is sleep
function unlock
我们从运行结果可以看到,当主进程把互斥量锁住后,子进程就不能对共享资源进行操作了。
四、互斥锁属性
线程和线程的同步对象(互斥量,读写锁,条件变量)都具有属性。在修改属性前都需要对该结构进行初始化。使用后要把该结构回收。我们用pthread_ mutexattr_init函数对pthread_mutexattr结构进行初始化,用pthread_mutexattr_destroy函数对该结构进行回收。
4
名称:: |
pthread_mutexattr_init/pthread_mutexattr_destroy |
功能: |
初始化/回收pthread_mutexattr_t结构 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutexattrattr_init(pthread_mutexattr_t *attr); int pthread_mutexattrattr_destroy( pthread_mutexattr_t *attr ); |
参数: |
attr pthread_mutexattr_t结构变量 |
返回值: |
若成功返回0,若失败返回错误编号。 |
pthread_mutexattr_init将属性对象的值初始化为缺省值。并分配属性对象占用的内存空间。
attr中pshared属性表示用这个属性对象创建的互斥锁的作用域,它的取值可以是PTHREAD_PROCESS_PRIVATE(缺省值,表示由这个属性对象创建的互斥锁只能在进程内使用)或PTHREAD_PROCESS_SHARED。
互斥量属性分为共享互斥量属性和类型互斥量属性。两种属性分别由不同的函数得到并由不同的函数进行修改。pthread_mutexattr_getpshared和pthread_mutexattr_setpshared函数可以获得和修改共享互斥量属性。pthread_mutexattr_gettype和pthread_mutexattr_settype函数可以获得和修改类型互斥量属性。下面我们分别介绍。
5
名称:: |
pthread_mutexattr_getpshared/pthread_mutexattr_setpshared |
功能: |
获得/修改共享互斥量属性 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutexattrattr_ getpshared ( const pthread_attr_t *restrict attr,int*restrict pshared); int pthread_mutexattrattr_ setpshared ( const pthread_attr_t *restrict attr,int pshared); |
参数: |
|
返回值: |
若成功返回0,若失败返回错误编号。 |
共享互斥量属性用于规定互斥锁的作用域。互斥锁的域可以是进程内的也可以是进程间的。pthread_mutexattrattr_ getpshared可以返回属性对象的互斥锁作用域属性。可以是以下值:PTHREAD_PROCESS_SHARED,PTHREAD_PROCESS_PRIVATE。如果互斥锁属性对象的pshared属性被置PTHREAD_PROCESS_SHARED。那么由这个属性对象创建的互斥锁将被保存在共享内存中,可以被多个进程中的线程共享。如果pshared属性被置为PTHREAD_PROCESS_PRIVATE,那么只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁。
6
名称:: |
pthread_mutexattr_gettype/pthread_mutexattr_settype |
功能: |
获得/修改类型互斥量属性 |
头文件: |
#include <pthread.h> |
函数原形: |
int pthread_mutexattrattr_ getpshared ( const pthread_attr_t *restrict attr,int*restrict pshared); int pthread_mutexattrattr_ setpshared ( const pthread_attr_t *restrict attr,int pshared); |
参数: |
|
返回值: |
若成功返回0,若失败返回错误编号。 |
pthread_mutexattr_gettype函数可以获得互斥锁类型属性。缺省的互斥锁类型属性是PTHREAD_MUTEX_DEFAULT。
合法的类型属性值有:
PTHREAD_MUTEX_NORMAL;
PTHREAD_MUTEX_ERRORCHECK;
PTHREAD_MUTEX_RECURSIVE;
PTHREAD_MUTEX_DEFAULT。
类型说明:
PTHREAD_MUTEX_NORMAL
这种类型的互斥锁不会自动检测死锁。如果一个线程试图对一个互斥锁重复锁定,将会引起这个线程的死锁。如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。
PTHREAD_MUTEX_ERRORCHECK
这种类型的互斥锁会自动检测死锁。如果一个线程试图对一个互斥锁重复锁定,将会返回一个错误代码。如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。
PTHREAD_MUTEX_RECURSIVE
如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁,一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的(作用域属性为PTHREAD_PROCESS_PRIVATE)。
PTHREAD_MUTEX_DEFAULT
这种类型的互斥锁不会自动检测死锁。如果一个线程试图对一个互斥锁重复锁定,将会引起不可预料的结果。如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。POSIX标准规定,对于某一具体的实现,可以把这种类型的互斥锁定义为其他类型的互斥锁。
五、应用互斥量需要注意的几点
1、互斥量需要时间来加锁和解锁。锁住较少互斥量的程序通常运行得更快。所以,互斥量应该尽量少,够用即可,每个互斥量保护的区域应则尽量大。
2、互斥量的本质是串行执行。如果很多线程需要领繁地加锁同一个互斥量,
则线程的大部分时间就会在等待,这对性能是有害的。如果互斥量保护的数据(或代码)包含彼此无关的片段,则可以特大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待时间就会减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则应尽量的少。