一、互斥量概念与使用

  • 概念:​可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据


互斥量的使用:

  • 互斥量从本质上说​是一把锁​,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量
  • 对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程释放该互斥锁
  • 如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能再次等待它重新变为可用(在这种方式下,每次只有一个线程可以向前执行)


二、互斥变量(pthread_mutex_t)

  • 互斥量数据类型:pthread_mutex_t

三、互斥变量的初始化与释放


①静态初始化

  • 直接把pthread_mutex_t互斥变量设置为常量​PTHREAD_MUTEX_INITIALIZER
  • 静态初始化互斥变量​只能拥有默认的互斥量属性​,不能设置其他互斥量属性(互斥量属性见文章:
  • 例如:

pthread_mutex_t  mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;

//或者
pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
*mutex=PTHREAD_MUTEX_INITIALIZER;



②动态初始化

  • 静态初始化互斥变量只能拥有默认互斥量属性,我们可以通过pthread_mutex_init函数来动态初始化互斥量,并且可以在初始化时选择设置互斥量的属性

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 返回值:成功返回0,否则返回错误编号

pthread_mutex_init:

  • 功能:​对互斥量进行初始化
  • 参数:
  • 参数1:​需要初始化互斥量
  • 参数2:​初始化时互斥量的属性。如果使用默认属性,此处填NULL(互斥量属性见文章:​​​

pthread_mutex_destroy:

  • 功能:​互斥量的反初始化
  • 参数:​互斥量
  • 备注(重点):​此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数

例如:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);

/*do something*/

pthread_mutex_destroy(&mutex);
pthread_mutex_t* mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex,NULL);

/*do something*/

pthread_mutex_destroy(mutex);
free(mutex);


四、互斥量的加锁与解锁

#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:​对互斥量进行解锁
  • pthead_mutex_trylock:​对互斥量进行尝试加锁(非阻塞)。如果互斥量处于未加锁状态,那么pthead_mutex_trylock就会锁住这个互斥量;如果此锁处于加锁状态,那么pthead_mutex_trylock就出错返回EBUSY,并且不会阻塞

例如去食堂吃饭:​如果食堂人太多(加锁),pthread_mutex_lock就会排队等待(阻塞);pthead_mutex_trylock是去食堂尝试(try)一下,如果人多就不排队(不阻塞)直接退出


五、演示案例

APUE编程:57---线程处理(互斥量:pthread_mutex_t)_互斥量的加锁

struct foo {
int f_count;
pthread_mutex_t f_lock;
int f_id;
};

struct foo *foo_alloc(int id)
{
struct foo *fp;

if ((fp == malloc(sizeof(struct foo))) != NULL) {
fp->f_count = 1;
fp->f_id = id;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
free(fp);
fp = NULL;
}
}

return fp;
}

void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}

void foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0) {
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destory(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}

六、超时互斥锁(pthread_mutex_timedlock)

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr);

// 返回值:成功返回0,否则返回错误编号.
  • 功能:​当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock允许绑定线程阻塞时间
  • 参数:
  • mutex:​尝试要加锁的互斥量
  • tsptr:​设置的超时时间
  • 特点:​在达到超时时间值时,如果还不能对互斥量成功加锁,那么就返回错误码ETIMEDOUT
  • 超时时间的注意:​超时指定愿意等待的绝对时间。这个时间值是一个绝对数而不是相对数。例如,假设愿意等待3分钟,不是把3分钟转换为timespec结构体传递给参数2,而是需要把当前时间加上3分钟再转换成timespec结构然后传递给参数2

APUE编程:57---线程处理(互斥量:pthread_mutex_t)_加锁_02


演示案例:下面给出了pthread_mutex_timedlock避免永久阻塞

#include <pthread.h>

int main(void)
{
int err;
struct timespec tout;
struct tm *tmp;
char buf[64];

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
printf("mutex is locked\n");
clock_gettime(CLOCK_REALTIME, &tout);
tmp = localtime(&tout.tv_sec);
strftime(buf, sizeof(buf), "%r", tmp);
printf("current time is %s\n", buf);
tout.tv_sec += 10; /* 10 seconds from now */

/* caution: this could lead to deadlock */
err = pthread_mutex_timedlock(&lock, &tout);
clock_gettime(CLOCK_REALTIME, &tout);
tmp = localtime(&tout.tv_sec);
strftime(buf, sizeof(buf), "%r", tmp);
printf("the time is now %s\n", buf);
if (err == 0)
printf("mutex locked again!\n");
else
printf("can’t lock mutex again: %s\n", strerror(err));
exit(0);
}

运行结果

APUE编程:57---线程处理(互斥量:pthread_mutex_t)_互斥量的加锁_03

  • 这个程序故意对它已有的互斥量进行加锁,目的是演示pthread_mutex_timedlock是如何工作的。不推荐在实际中使用这种方法,因为会导致死锁
  • 注意:​阻塞的时间可能会有所不同,造成不同的原因有多种:开始时间可能在某秒的中间位置,系统时钟的精度可能不足以精确到支持我们指定的超时时间值,或者在程序继续运行前,调度延迟可能会增加时间值


七、演示案例

  • 主线程写,子线程读
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define MAXSIZE 1024

void *func(void *arg);
char buff[MAXSIZE];
pthread_mutex_t mutex;

int main()
{
void* exit_value;

pthread_t tid;
//create thread
if(pthread_create(&tid,NULL,func,NULL)!=0){
perror("pthread_create");
exit(EXIT_FAILURE);
}

//init mutex
if(pthread_mutex_init(&mutex,NULL)!=0){
perror("pthread_mutex_init");
exit(EXIT_FAILURE);
}


while(1)
{
pthread_mutex_lock(&mutex); //lock
printf("Enter(end to quit):");
fflush(stdout);
scanf("%s",buff);
if(strncmp(buff,"end",3)==0){
pthread_mutex_unlock(&mutex); //unlock
break;
}
pthread_mutex_unlock(&mutex); //unlock
sleep(1);
}


//等待进程结束
printf("join......\n");
if(pthread_join(tid,&exit_value)!=0){
perror("pthread_join");
exit(EXIT_FAILURE);
}

//destory mutex
pthread_mutex_destroy(&mutex);
exit(0);
}

void *func(void *arg)
{
sleep(1);

while(1)
{
pthread_mutex_lock(&mutex);//lock
if(strlen(buff)!=0){
if(strncmp(buff,"end",3)==0){
pthread_mutex_unlock(&mutex);//unlock
pthread_exit(0);
}
printf("read %d bytes\n",strlen(buff));
buff[0]='\0';
pthread_mutex_unlock(&mutex); //unlock
sleep(1);
}
}
}

 APUE编程:57---线程处理(互斥量:pthread_mutex_t)_互斥量_04

八、演示案例

  • 两个子线程对主线程中的数字进行读取了递减,数字变为0之后,两个子进程都结束
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>


pthread_mutex_t *mutex;
int success=0;
int error=1;
void *func(void *arg);

void free_mutex(pthread_mutex_t *mutex);

int main()
{
int num=10;
int *retValue=(int*)malloc(sizeof(int));
pthread_t tid1,tid2;

mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
if(pthread_mutex_init(mutex,NULL)!=0){
perror("pthread_mutex_init error");
free_mutex(mutex);
exit(EXIT_FAILURE);
}

if(pthread_create(&tid1,NULL,func,(void*)&num)!=0){
perror("thread 1 create error");
free_mutex(mutex);
exit(EXIT_FAILURE);
}
if(pthread_create(&tid2,NULL,func,(void*)&num)!=0){
perror("thread 2 create error");
free_mutex(mutex);
exit(EXIT_FAILURE);
}

if(pthread_join(tid1,(void**)&retValue)!=0){
perror("pthread_join 1 error");
free_mutex(mutex);
exit(EXIT_FAILURE);
}else{
if((*retValue)==0){
printf("Thread1 return success\n");
}else{
printf("Thread1 return error\n");
}
}
if(pthread_join(tid2,(void**)&retValue)!=0){
perror("pthread_join 2 error");
free_mutex(mutex);
exit(EXIT_FAILURE);
}else{
if((*retValue)==0){
printf("Thread2 return success\n");
}else{
printf("Thread2 return error\n");
}
}

if(pthread_mutex_destroy(mutex)!=0){
perror("pthread_mutex_destroy error");
exit(EXIT_FAILURE);
}

free_mutex(mutex);
//pthread_join会自动释放这个内存,如果此处还释放retValue,程序就会出错
/*if(retValue){
free(retValue);
retValue=NULL;
}*/

exit(EXIT_SUCCESS);
}

void *func(void *arg)
{
int *num=(int*)(arg);
while(1)
{
if(pthread_mutex_lock(mutex)!=0){
printf("Thread %lu:pthread_mutex_destroy error",pthread_self());
pthread_exit(&error);
}
if((*num)<=0){
if(pthread_mutex_unlock(mutex)!=0){
printf("Thread %lu:pthread_mutex_unlock error",pthread_self());
pthread_exit(&error);
}
break;
}

printf("Thread %lu:current num is %d\n",pthread_self(),*num);
(*num)--;

if(pthread_mutex_unlock(mutex)!=0){
printf("Thread %lu:pthread_mutex_unlock error",pthread_self());
pthread_exit(&error);
}
sleep(1);
}

pthread_exit(&success);
}

void free_mutex(pthread_mutex_t *mutex)
{
if(mutex!=NULL){
free(mutex);
mutex=NULL;
}
}

APUE编程:57---线程处理(互斥量:pthread_mutex_t)_加锁_05