linux下为了多线程同步,通常用到锁的概念。
posix下抽象了一个锁类型的结构:ptread_mutex_t。通过对该结构的操作,来判断资源是否可以访问。顾名思义,加锁(lock)后,别人就无法打开,只有当锁没有关闭(unlock)的时候才能访问资源。
即对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。
要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。
l 头文件:
#include <pthread.h>
l 函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
l 函数作用:
该函数用于C函数的多线程编程中,互斥锁的初始化。
pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空(NULL),则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为未锁住态。
l 互斥锁pthread_mutex_t的使用:
1. 互斥锁创建和销毁
有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
2.互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3.其他锁操作
锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
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而不是挂起等待。
4.死锁
死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。
总体来讲, 有几个不成文的基本原则:
对共享资源操作前一定要获得锁。
完成操作以后一定要释放锁。
尽量短时间地占用锁。
如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
线程错误返回时应该释放它所获得的锁。
下面是一段测试代码,创建两个线程,分别访问全局变量gnum,并且修改它,打印出来.
1 /* mutex.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <pthread.h> 5 #include <errno.h> 6 7 /* 全局变量 */ 8 int gnum = 0; 9 /* 互斥量 */ 10 pthread_mutex_t mutex; 11 12 /* 声明线程运行服务程序. */ 13 static void pthread_func_1(void); 14 static void pthread_func_2(void); 15 16 int main (void) 17 { 18 /*线程的标识符*/ 19 pthread_t pt_1 = 0; 20 pthread_t pt_2 = 0; 21 int ret = 0; 22 23 /* 互斥初始化. */ 24 pthread_mutex_init(&mutex, NULL); 25 /*分别创建线程1、2*/ 26 ret = pthread_create(&pt_1, //线程标识符指针 27 NULL, //默认属性 28 (void*)pthread_func_1, //运行函数 29 NULL); //无参数 30 if (ret != 0) 31 { 32 perror ("pthread_1_create"); 33 } 34 35 ret = pthread_create(&pt_2, //线程标识符指针 36 NULL, //默认属性 37 (void *)pthread_func_2, //运行函数 38 NULL); //无参数 39 if (ret != 0) 40 { 41 perror ("pthread_2_create"); 42 } 43 /*等待线程1、2的结束*/ 44 pthread_join(pt_1, NULL); 45 pthread_join(pt_2, NULL); 46 47 printf ("main programme exit!/n"); 48 return 0; 49 } 50 51 /*线程1的服务程序*/ 52 static void pthread_func_1(void) 53 { 54 int i = 0; 55 56 for (i=0; i<3; i++) { 57 printf ("This is pthread_1!/n"); 58 pthread_mutex_lock(&mutex); /* 获取互斥锁 */ 59 /* 注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用. */ 60 sleep (1); 61 /*临界资源*/ 62 gnum++; 63 printf ("Thread_1 add one to num:%d/n", gnum); 64 pthread_mutex_unlock(&mutex); /* 释放互斥锁. */ 65 } 66 67 pthread_exit(NULL); 68 } 69 70 /*线程2的服务程序*/ 71 static void pthread_func_2(void) 72 { 73 int i = 0; 74 75 for (i=0; i<5; i++) { 76 printf ("This is pthread_2!/n"); 77 pthread_mutex_lock(&mutex); /* 获取互斥锁. */ 78 /* 注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用. */ 79 sleep(1); 80 /* 临界资源. */ 81 gnum++; 82 printf ("Thread_2 add one to num:%d/n",gnum); 83 pthread_mutex_unlock(&mutex); /* 释放互斥锁. */ 84 } 85 86 pthread_exit (NULL); 87 }