Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

时间:2022-03-03 11:39:47

相互排斥锁通信机制

基本原理

相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下:

(1)在訪问该资源之前须要首先申请相互排斥锁,假设锁处于开状态,则申请得到锁并马上上锁(关),防止其它进程訪问资源,假设锁处于关,则默认堵塞等待.

(2)仅仅有锁定该相互排斥锁的进程才干释放该相互排斥锁.

相互排斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有详细的定义。

相互排斥量初始化和销毁

在使用相互排斥锁之前须要定义相互排斥锁(全局变量),定义代码:

pthread_mutex lock;
/* Initialize a mutex.  */
int pthread_mutex_init (pthread_mutex_t *__mutex,\ // 指向要初始化的相互排斥锁的指针
__const pthread_mutexattr_t *__mutexattr); // 指向相互排斥锁属性对象的指针,设null为默认属性 /* Destroy a mutex. */
int pthread_mutex_destroy (pthread_mutex_t *__mutex);

上面两个函数分别因为相互排斥量的初始化和销毁。

假设相互排斥量是静态分配的,能够通过常量进行初始化,例如以下:

#define PTHREAD_MUTEX_INITIALIZER {{0, }}  // 系统定义的,无需声明
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

当然也能够通过pthread_mutex_init()进行初始化。对于动态分配的相互排斥量因为不能直接赋值进行初始化就仅仅能採用这样的方式进行初始化,pthread_mutex_init()的第二个參数是相互排斥量的属性,假设採用默认的属性设置,能够传入NULL。

当不在须要使用相互排斥量时,须要调用pthread_mutex_destroy()销毁相互排斥量所占用的资源。

相互排斥量的属性设置

/* 初始化相互排斥量属性对象 */
int pthread_mutexattr_init (pthread_mutexattr_t *__attr); /* 销毁相互排斥量属性对象 */
int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr); /* 获取相互排斥量属性对象在进程间共享与否的标志 */
int pthread_mutexattr_getpshared (__const pthread_mutexattr_t *__restrict __attr, \
int *__restrict __pshared); /* 设置相互排斥量属性对象,标识在进程间共享与否 */
int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr, int __pshared);

相互排斥量在初始化的时候pthread_mutex_init的第二个參数是相互排斥量的属性,假设为NULL空指针,那么就使用默认属性。

相互排斥量属性的数据类型为pthread_mutexattr_t,它的初始化和销毁和相互排斥量类似。一旦相互排斥量属性对象被初始化后,就能够通过调用不同的函数启用或禁止特定的属性。这里列出了一个设置特定属性的函数:pthread_mutexattr_setpshared,能够用于指定相互排斥量在不同进程间共享,这样能够通过相互排斥量来同步不同的进程,当然前提是该相互排斥量位于进程间的共享内存区。

pthread_mutexattr_setpshared()函数的第二个參数__pshared用于设定是否进程间共享,其值能够是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,后者是设置进程间共享。

以下是使相互排斥量能够在进程间共享的大致过程:

pthread_mutex_t *pSharedMutex;  //指向共享内存区的相互排斥量
pthread_mutexattr_t mutexAttr; //相互排斥量属性 pSharedMutex = /*一个指向共享内存区的指针*/; pthread_mutexattr_init(&mutexAttr);
pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(pSharedMutex, &mutexAttr);

相互排斥量的申请使用

/* Try locking a mutex.  */
int pthread_mutex_trylock (pthread_mutex_t *__mutex); /* Lock a mutex. */
int pthread_mutex_lock (pthread_mutex_t *__mutex); /* Unlock a mutex. */
int pthread_mutex_unlock (pthread_mutex_t *__mutex);

这几个函数都非常easy,通过pthread_mutex_lock()函数获得訪问共享资源的权限,假设已经有其它线程锁住相互排斥量,那么该函数会是线程堵塞指定该相互排斥量解锁为止。 pthread_mutex_trylock()是相应的非堵塞函数,假设相互排斥量已被占用,它会返回一个EBUSY错误。訪问完共享资源后,一定要通过pthread_mutex_unlock() 函数,释放占用的相互排斥量。同意其它线程訪问该资源。

这里要强调的是:相互排斥量是用于上锁的,不能用于等待。

简单说就是,相互排斥量的使用流程应该是:线程占用相互排斥量,然后訪问共享资源,最后释放相互排斥量。而不应该是:线程占用相互排斥量,然后推断资源是否可用,假设不可用,释放相互排斥量,然后反复上述过程。这样的行为称为轮转或轮询,是一种浪费CPU时间的行为。

应用演示样例

以下的程序使用了两个同进程的线程,一个线程负责从标准设备读入数据存储在共享数据区,还有一个负责将读入的数据输出到标准设备.事实上是一个生产者消费者的变形.

  1. 处理输入操作的线程在接受用户信息的时候必须相互排斥使用该资源,不能被打断,堵塞其它线程的进行
  2. 输出线程也不能被打断,须要先锁定相互排斥锁,操作完毕后释放,
  3. 程序结束,销毁相互排斥锁.

代码例如以下:

/*************************************************************************
> File Name: pthread_exp.c
> Author:SuooL
> Mail:1020935219@qq.com || hu1020935219@gmail.com
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月15日 星期三 08时42分45秒
> Description:
************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h> void *thread_function(void *arg); pthread_mutex_t work_mutex; // 定义全局相互排斥锁对象 #define WORK_SIZE 1024
char work_area[WORK_SIZE]; // 全局共享数据区
int time_to_exit = 0; // 标志变量 // main函数
int main(int argc,char *argv[])
{
int res;
pthread_t a_thread; // 线程
void *thread_result;
res = pthread_mutex_init(&work_mutex, NULL); //init mutex 初始化相互排斥锁
if (res != 0)
{
perror("Mutex initialization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread, NULL, thread_function, NULL);//create new thread
if (res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
pthread_mutex_lock(&work_mutex); //lock the mutex,before get the input
printf("Input some text. Enter 'end' to finish\n");
while(!time_to_exit)
{
fgets(work_area, WORK_SIZE, stdin); //get a string from stdin
pthread_mutex_unlock(&work_mutex); //unlock the mutex
while(1)
{
pthread_mutex_lock(&work_mutex); //lock the mutex
if (work_area[0] != '\0') // check out if the info input has been output
{
pthread_mutex_unlock(&work_mutex); //unlock the mutex
sleep(1);
}
else // if has been output, break and goto next iput
{
break;
}
}
}
pthread_mutex_unlock(&work_mutex); // unlock the mutex
printf("\nWaiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result); // wait for the other pthread end
if (res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
pthread_mutex_destroy(&work_mutex); // destory the mutex
exit(EXIT_SUCCESS);
} // the pthread run function
void *thread_function(void *arg)
{
sleep(1);
pthread_mutex_lock(&work_mutex); // lock the mutex 抢占资源
while(strncmp("end", work_area, 3) != 0) // 推断是否为结束信息
{
printf("You input %d characters\n", strlen(work_area) -1); // 输出输入信息
printf("the characters is %s",work_area);
work_area[0] = '\0'; // 设置数据区,表示输入已被输出
pthread_mutex_unlock(&work_mutex); // 解锁
sleep(1);
pthread_mutex_lock(&work_mutex); // 上锁
while (work_area[0] == '\0' ) // 推断是否被输出
{ // 假设是\0则没有输入,等待主进程输入
pthread_mutex_unlock(&work_mutex); // 解锁
sleep(1); // 等待
pthread_mutex_lock(&work_mutex); // 上锁,再次检測
}
}
time_to_exit = 1; // 设置主线程退出信号
work_area[0] = '\0'; // 标志输出
pthread_mutex_unlock(&work_mutex); // 世界所
pthread_exit(0); // 线程退出
}

执行结果:

Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

读写锁通信机制

读写锁和相互排斥量(相互排斥锁)非常类似,是还有一种线程同步机制,但不属于POSIX标准,能够用来同步同一进程中的各个线程。当然假设一个读写锁存放在多个进程共享的某个内存区中,那么还能够用来进行进程间的同步.

由于对数据的读写应用中,非常多情况都是大量的读操作,而较少有写操作,比如对数据库的訪问等,显然这样使用相互排斥锁会非常影响效率,为了满足这种需求,POSIX线提供了读写锁机制.模式例如以下:

  1. 假设当前进程读数据,其他的进程也能够进行读操作,但不能写操作
  2. 假设当前进程写数据,则全部其他进程堵塞等待

和相互排斥量不同的是:相互排斥量会把试图进入已保护的临界区的线程都堵塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来推断是否同意线程进入。

相对相互排斥量仅仅有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁

读写锁的使用规则:

  • 仅仅要没有写模式下的加锁,随意线程都能够进行读模式下的加锁;
  • 仅仅有读写锁处于不加锁状态时,才干进行写模式下的加锁;

读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁很适合读数据的频率远大于写数据的频率从的应用中。这样能够在不论什么时刻执行多个读线程并发的执行,给程序带来了更高的并发度。

定义代码例如以下:

pthread_rwlock_t ralock;

初始化和销毁读写锁

/* Initialize read-write lock  */
int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock, // 指向要初始化的读写锁指针
__const pthread_rwlockattr_t *__restrict __attr); // 指向属性对象的指针 /* Destroy read-write lock */
extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock); //返回值:成功返回0,否则返回错误代码

上面两个函数分别因为读写锁的初始化和销毁。和相互排斥量,条件变量一样,假设读写锁是静态分配的,能够通过常量进行初始化,例如以下:

<span style="font-size:12px;">pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;</span>

也能够通过pthread_rwlock_init()进行初始化。对于动态分配的读写锁因为不能直接赋值进行初始化,仅仅能通过这样的方式进行初始化。pthread_rwlock_init()第二个參数是读写锁的属性,假设採用默认属性,能够传入空指针NULL。

那么当不在须要使用时及释放(自己主动或者手动)读写锁占用的内存之前,需要调用pthread_rwlock_destroy()进行销毁读写锁占用的资源。

读写锁的属性设置

/* 初始化读写锁属性对象 */
int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr); /* 销毁读写锁属性对象 */
int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr); /* 获取读写锁属性对象在进程间共享与否的标识*/
int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t * __restrict __attr,
int *__restrict __pshared); /* 设置读写锁属性对象,标识在进程间共享与否 */
int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr, int __pshared); 返回值:成功返回0,否则返回错误代码

这个属性设置和相互排斥量的基本一样,详细能够參考上面的相互排斥量的设置相互排斥量的属性设置

读写锁的申请使用

/* 读模式下加锁  */
int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock); /* 非堵塞的读模式下加锁 */
int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K
/* 限时等待的读模式加锁 */
int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,
__const struct timespec *__restrict __abstime);
# endif /* 写模式下加锁 */
int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock); /* 非堵塞的写模式下加锁 */
int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K
/* 限时等待的写模式加锁 */
int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,
__const struct timespec *__restrict __abstime);
# endif /* 解锁 */
int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否则返回错误代码

(1)pthread_rwlock_rdlock()系列函数

pthread_rwlock_rdlock()用于以读模式即共享模式获取读写锁,假设读写锁已经被某个线程以写模式占用,那么调用线程就被堵塞。在实现读写锁的时候能够对共享模式下锁的数量进行限制(眼下不知怎样限制)。

pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一差别就是,在无法获取读写锁的时候,调用线程不会堵塞,会马上返回,并返回错误代码EBUSY。

pthread_rwlock_timedrdlock()是限时等待读模式加锁,时间參数struct timespec * __restrict __abstime也是绝对时间,和条件变量的pthread_cond_timedwait()使用基本一致,详细能够參考pthread_cond_timedwait()

(2)pthread_rwlock_wrlock()系列函数

pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,假设读写锁已经被其它线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入堵塞状态。

pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会马上返回,并返回错误代码EBUSY。

pthread_rwlock_timedwrlock()是限时等待写模式加锁,也和条件变量的pthread_cond_timedwait()使用基本一致,详细能够參考pthread_cond_timedwait()

(3)pthread_rwlock_unlock()

不管以共享模式还是独占模式获得的读写锁,都能够通过调用pthread_rwlock_unlock()函数进行释放该读写锁。

读写锁应用

以下的程序使用读写锁实现4个线程读写一段数据.

当中两个线程读数据,两个线程写数据.

从结果看出,随意进程写数据的时候,将堵塞全部其它进程的操作,但在某一进程读数据的时候,其它进程仍然能够读得数据.

编译执行结果例如以下:

Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

代码例如以下:

/*************************************************************************
> File Name: pthread_rwlock_exp.c
> Author:SuooL
> Mail:1020935219@qq.com || hu1020935219@gmail.com
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月15日 星期三 09时20分45秒
> Description:
************************************************************************/
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bits/pthreadtypes.h> static pthread_rwlock_t rwlock; // 读写锁对象 #define WORK_SIZE 1024
char work_area[WORK_SIZE]; // 全局共享数据区
int time_to_exit; // 标志变量 // 子线程运行的函数声明
void *thread_function_read_o(void *arg);
void *thread_function_read_t(void *arg);
void *thread_function_write_o(void *arg);
void *thread_function_write_t(void *arg); // main 函数
int main(int argc,char *argv[])
{
int res;
pthread_t a_thread,b_thread,c_thread,d_thread; // 线程定义
void *thread_result; // 指针定义 res=pthread_rwlock_init(&rwlock,NULL); // 读写锁定义
if (res != 0)
{
perror("rwlock initialization failed");
exit(EXIT_FAILURE);
}
// 创建子线程
res = pthread_create(&a_thread, NULL, thread_function_read_o, NULL);//create new thread
if (res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
} res = pthread_create(&b_thread, NULL, thread_function_read_t, NULL);//create new thread
if (res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&c_thread, NULL, thread_function_write_o, NULL);//create new thread
if (res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&d_thread, NULL, thread_function_write_t, NULL);//create new thread
if (res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
// 线程等待
res = pthread_join(a_thread, &thread_result);
if (res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
res = pthread_join(b_thread, &thread_result);
if (res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
res = pthread_join(c_thread, &thread_result);
if (res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
res = pthread_join(d_thread, &thread_result);
if (res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
// 读写锁销毁
pthread_rwlock_destroy(&rwlock);
exit(EXIT_SUCCESS);
} void *thread_function_read_o(void *arg) // 读线程one
{
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",work_area);
pthread_rwlock_unlock(&rwlock); // 解锁
sleep(2);
pthread_rwlock_rdlock(&rwlock); // 获取所
while (work_area[0] == '\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_function_read_t(void *arg) // 读线程two,同one
{
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_function_write_o(void *arg) // 写线程one
{
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.\nInput some text. Enter 'end' to finish\n");
fgets(work_area, WORK_SIZE, stdin); // 获取标准输入写到是数据区
pthread_rwlock_unlock(&rwlock); // 解锁
sleep(15); // 等待
}
pthread_rwlock_unlock(&rwlock); // 解锁
pthread_exit(0); // 线程退出
} void *thread_function_write_t(void *arg) // 写线程two,同one
{
sleep(10);
while(!time_to_exit)
{
pthread_rwlock_wrlock(&rwlock);
printf("this is write thread two.\nInput 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);
}