1.互斥锁
在线程实际运行过程中,我们经常需要多个线程保持同步。这时可以用互斥锁来完成任务。
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);
其中:
mutex:所需创建的锁;
attr:创建锁的属性。一般默认为NULL,分为以下几个属性:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后
按优先级获得锁。这种锁策略保证了资源分配的公平性;
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求
,则在加锁线程解锁时重新竞争;
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP
类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁;
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争;
函数成功执行后,互斥锁被初始化为锁住态。
1.2锁操作
对锁的操作主要包括加锁 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.3锁销毁
创建的互斥锁在不使用的时候需要消耗,不然会造成系统资源的流失,其函数原型如下:
int pthread_mutexattr_destroy (pthread_mutex_t *mutex);
1.4示例代码
#include
#include
#include
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;
}
2.读写锁
读写锁: 我们在编写多线程的时候,我们可能需要经常去读取某个共享数据变量,但是相对要改写这个变量的机会相对较少。在读的过程中,往往伴随着查找的操作,中间耗时很长,给这种代码加锁,会极大的降低我们程序的效率。所以提出了读写锁。
读写锁具有写独占,读共享,写锁优先级高的特性。
2.1.锁的初始化
读写锁可以静态或动态的获取,其中静态获取的方式如下:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
其中 PTHREAD_RWLOCK_INITIALIZER 是 “pthread_rwlock.h” 中一段宏定义
动态获取的函数原型如下:
int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr);
其中:
rw:读写锁;
attr:设置锁的属性,如下:
PTHREAD_PROCESS_SHARED:允许可访问用于分配读写锁的内存的任何线程对读写锁进行处理。即使该锁是在由多个进程共享的内存
中分配的,也允许对其进行处理;
PTHREAD_PROCESS_PRIVATE:读写锁只能由某些线程处理,这些线程与初始化该锁的线程在同一进程中创建。如果不同进程的线程
尝试对此类读写锁进行处理,则其行为是不确定的。缺省值为 PTHREAD_PROCESS_PRIVATE;
返回值:成功返回0,错误返回具体错误代码。
2.2锁的获取与释放
相关函数原型如下:
获取一个读出锁,如果对应的读写锁已由某个写入者持有,那就阻塞调用线程:
int pthread_rwlock_rdlock(pthread_rwlock_t *);
获取一个写入锁,如果对应的读写锁已由另一个写入者持有,或者已由一个或多个读出者持有,那就阻塞调用线程:
int pthread_rwlock_wrlock(pthread_rwlock_t *);
释放一个读出锁或写入锁:
int pthread_rwlock_unlock(pthread_rwlock_t *);
下面两个函数尝试获取一个读出锁或写入锁,但是如果该锁不能马上取得,那就返回一个EBUSY错误,而不是把调用线程投入睡眠:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int pthread_rwlock_trywrlock(pthread_rwlock_t *);
返回值:成功返回0,错误返回具体错误代码。
2.3锁的销毁
函数原型如下:
int pthread_rwlock_destroy(pthread_rwlock_t *);
返回值:成功返回0,错误返回具体错误代码。
2.4示例代码
#include
#include
#include
#include
#include
#include
#define WORK_SIZE 1024
static pthread_rwlock_t rwlock;
char work_area[WORK_SIZE];
int time_to_exit;
void *thread_to_read_one(void *arg);
void *thread_to_read_two(void *arg);
void *thread_to_write_one(void *arg);
void *thread_to_write_two(void *arg);
void *thread_to_read_one(void *arg)
{
printf("thread read one try to get lock\n");
pthread_rwlock_rdlock(&rwlock);
while(strncmp("end",work_area,3) != 0){
printf("this is thread read one\n");
printf("the characters is %s\n",work_area);
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
while(work_area[0] == '\0'){
pthread_rwlock_unlock(&rwlock);
sleep(2);
pthread_rwlock_rdlock(&rwlock);
}
}
pthread_rwlock_unlock(&rwlock);
time_to_exit = 1;
pthread_exit(0);
}
void *thread_to_read_two(void *arg)
{
printf("thread read one try to get lock\n");
pthread_rwlock_rdlock(&rwlock);
while(strncmp("end",work_area,3) != 0){
printf("this is thread read two\n");
printf("the characters is %s\n",work_area);
pthread_rwlock_unlock(&rwlock);
sleep(5);
pthread_rwlock_rdlock(&rwlock);
while(work_area[0] == '\0'){
pthread_rwlock_unlock(&rwlock);
sleep(5);
pthread_rwlock_rdlock(&rwlock);
}
}
pthread_rwlock_unlock(&rwlock);
time_to_exit = 1;
pthread_exit(0);
}
void *thread_to_write_one(void *arg)
{
printf("this is write thread one try to get lock\n");
while(!time_to_exit){
pthread_rwlock_wrlock(&rwlock);
printf("this is write thread one\n,input some text,enter 'end' to finish\n");
fgets(work_area,WORK_SIZE,stdin);
pthread_rwlock_unlock(&rwlock);
sleep(15); // forget sleep,so write always
}
pthread_rwlock_unlock(&rwlock);
pthread_exit(0);
}
void *thread_to_write_two(void *arg)
{
sleep(10);
while(!time_to_exit){
pthread_rwlock_wrlock(&rwlock);
printf("this is write thread two\n input some text,enter 'end' to finish\n");
fgets(work_area,WORK_SIZE,stdin);
pthread_rwlock_unlock(&rwlock);
sleep(20);
}
pthread_rwlock_unlock(&rwlock);
pthread_exit(0);
}
int main(int argc,char *argv[])
{
int retval;
pthread_t a_thread,b_thread,c_thread,d_thread;
void *thread_result;
retval = pthread_rwlock_init(&rwlock,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&a_thread,NULL,thread_to_read_one,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&b_thread,NULL,thread_to_read_two,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&c_thread,NULL,thread_to_write_one,NULL);
if(retval != 0){
exit(1);
}
retval = pthread_create(&d_thread,NULL,thread_to_write_two,NULL);
if(retval != 0){
exit(1);
}
pthread_join(a_thread,NULL);
pthread_join(b_thread,NULL);
pthread_join(c_thread,NULL);
pthread_join(d_thread,NULL);
pthread_rwlock_destroy(&rwlock);
printf("main thread will exit\n");
return 0;
}
3.文件锁
文件锁是用于解决资源的共享使用的一种机制:当多个用户需要共享一个文件时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括建议性锁和强制性锁:
建议性锁:要求每个使用上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁,它们依靠程序员遵守这个规定。
强制性锁:是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux中,实现文件上锁的函数有fcntl()和flock(),下面来分别做介绍。
3.1 fcntl实现文件锁
函数原型如下:
int fcntl(int fd, int cmd, struct flock *lock);
其中:
fd:文件描述符
cmd:可选参数如下:
F_DUPFD:复制一个现存的描述符
F_GETFD:获得fd的close-on-exec(执行时关闭)文件描述符标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态
F_SETFD:设置close-on-exec 标志,该标志由参数arg 的FD_CLOEXEC位决定
F_GETFL:得到open设置的标志
F_SETFL :改变open设置的标志
F_GETLK:根据lock参数值,决定是否可以上文件锁
F_SETLK:设置lock参数值的文件锁
flock :数据结构如下:
struct flock {
short l_type; /* 锁类型: F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK (删除锁)*/
short l_whence; /* SEEK_SET(起始),SEEK_CUR(当前), SEEK_END(结尾) */
off_t l_start; /* 根据l_whence决定偏移量,可以为负数 */
off_t l_len; /* 锁定字节数,0 代表直到EOF */
pid_t l_pid; /* 阻止我们取得锁的pid (F_GETLK only) */
};
示例代码如下:
int fd = open(“./test.txt”, O_RDWR | O_CREAT, LOCKMODE);
struct flock fl;
fl.l_type = F_WRLCK; /* write lock */
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0; //lock the whole file
fl.l_pid = getpid();
fcntl(fd, F_SETLK, &fl);
3.2 flock实现文件锁
函数原型如下:
int flock(int fd, int operation);
其中:
fd:打开的文件描述符;
operation:参数可选值如下:
LOCK_SH:放置共享锁;
LOCK_EX: 放置互斥锁;
LOCK_UN:解锁;
LOCK_NB:非阻塞锁请求,可如上述三个参数联合使用;
在默认情况下,如果另一进程已经持有了文件上的一个不兼容的锁,那么flock()会阻塞,如果设置成非阻塞模式,那么将返回-1,errno设置成 EWOULDBLOCK。
任意数量的进程可以持有文件的共享锁,但在同一时刻只能有一个进程拥有互斥锁,一旦一个进程拥有互斥锁,其他进程的共享锁请求也会被拒绝。
示例代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1000
char *currTime(const char *fmt);
void errExit(char *msg)
{
perror(msg);
exit(1);
}
char * currTime(const char *format)
{
static char buf[BUF_SIZE];
time_t t;
size_t s;
struct tm *tm;
t = time(NULL);
tm = localtime(&t);
if (tm == NULL)
return NULL;
s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);
return (s == 0) ? NULL : buf;
}
int main(int argc, char *argv[])
{
int fd, lock;
const char *lname;
if (argc < 3 || strcmp(argv[1], "--help") == 0 || strchr("sx", argv[2][0]) == NULL){
printf("%s file lock [sleep-time]\n"
" 'lock' is 's' (shared) or 'x' (exclusive)\n"
" optionally followed by 'n' (nonblocking)\n"
" 'sleep-time' specifies time to hold lock\n", argv[0]);
return 0;
}
lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
if (argv[2][1] == 'n')
lock |= LOCK_NB;
fd = open(argv[1], O_RDONLY); /* Open file to be locked */
if (fd == -1)
errExit("open");
lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";
printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
currTime("%T"));
if (flock(fd, lock) == -1) {
if (errno == EWOULDBLOCK){
printf("PID %ld: already locked - bye!", (long) getpid());
return 1;
}else{
printf("flock (PID=%ld)", (long) getpid());
return 1;
}
}
printf("PID %ld: granted %s at %s\n", (long) getpid(), lname, currTime("%T"));
sleep((argc > 3) ? atoi(argv[3]) : 10);
printf("PID %ld: releasing %s at %s\n", (long) getpid(), lname, currTime("%T"));
if (flock(fd, LOCK_UN) == -1)
errExit("flock");
exit(EXIT_SUCCESS);
}
4.自旋锁
自旋锁是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能在做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。