线程同步 (Linux应用编程篇)

线程的主要优势在于,资源的共享性,譬如通过全局变量来实现信息共享,不过这种便捷的共享是有代价的,那就是多个线程并发访问共享数据所导致的数据不一致的问题,可以采用同步技术解决资源共享带来的竞争

一、线程同步
  • 线程同步是为了对共享资源的访问进行保护。
  • 保护的目的是为了解决数据一致性的问题。
  • 出现数据一致性问题其本质在于进程中的多个线程对共享资源的并发访问(同时访问)
  • 当一个线程修改变量时,其它的线程在读取这个变量时可能会看到不一致的值,就得需要 Linux 提供的一些方法,也就是接下来将要向大家介绍的线程同步技术,来实现同一时间只允许一个线程访问该变量,防止出现并发访问的情况、消除数据不一致的问题
    在这里插入图片描述
    在这里插入图片描述
  • 线程的主要优势在于,资源的共享性譬如通过全局变量来实现信息共享。不过这种便捷的共享是有代价的,必须确保多个线程不会同时修改同一变量、或者某一线程不会读取正由其它线程修改的变量,也就是必须确保不会出现对共享资源的并发访问。 Linux系统提供了多种用于实现线程同步的机制,常见的方法有:互斥锁、条件变量、自旋锁以及读写锁等
二、互斥锁
  • 互斥锁(mutex)又叫互斥量,从本质上说是一把锁,在访问共享资源之前对互斥锁进行上锁,在访问完成后释放互斥锁(解锁);对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒,它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能再次陷入阻塞,等待下一次解锁
    互斥锁初始化
  • 1、使用PTHREAD_MUTEX_INITIALIZER 宏初始化互斥锁
# define PTHREAD_MUTEX_INITIALIZER \ 
   { { 0, 0, 0, 0, 0, __PTHREAD_SPINS, { 0, 0 } } } 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
  • 2、使用pthread_mutex_init()函数初始化互斥锁
/*
@	#include <pthread.h> 
@	mutex:参数mutex是一个 pthread_mutex_t类型指针,指向需要进行初始化操作的互斥锁对象;
@	attr:参数attr是一个pthread_mutexattr_t类型指针,指向一个pthread_mutexattr_t类型对象,该对象用于定义互斥锁的属性若将参数 attr设置为NULL,则表示将互斥锁的属性设置为默认值,在这种情况下其实就等价于PTHREAD_MUTEX_INITIALIZER这种方式初始化,而不同之处在于,使用宏不进行错误检查
@	返回值:成功返回0;失败将返回一个非 0的错误码。 
*/
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)

//=======================pthread_mutex_init()函数对互斥锁进行初始化示例:
pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL); 

//或者这样初始化 
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t)); 
pthread_mutex_init(mutex, NULL); 

** 互斥锁加锁和解锁**

  • 互斥锁初始化之后,处于一个未锁定状态,调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁
  • 调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁
  • 调用 pthread_mutex_lock()函数对互斥锁进行上锁,如果互斥锁处于未锁定状态,则此次调用会上锁成功,函数调用将立马返回;如果互斥锁此时已经被其它线程锁定了,那么调用 pthread_mutex_lock()会一直阻塞,直到该互斥锁被解锁,到那时,调用将锁定互斥锁并返回。
/*
@	#include <pthread.h> 
@	参数 mutex 指向互斥锁对象;
@	;pthread_mutex_lock()和pthread_mutex_unlock()在调用成功时返回 0;失败将返回一个非 0值的错误码
*/
int pthread_mutex_lock(pthread_mutex_t *mutex); 
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
  • 以下行为均属错误:
    • 对处于未锁定状态的互斥锁进行解锁操作
    • 解锁由其它线程锁定的互斥锁。
  • 如果有多个线程处于阻塞状态等待互斥锁被解锁,当互斥锁被当前锁定它的线程调用pthread_mutex_unlock()函数解锁后,这些等待着的线程都会有机会对互斥锁上锁,但无法判断究竟哪个线程会如愿以偿!
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_mutex_t mutex; 
static int g_count = 0; 
 
static void *new_thread_start(void *arg) 
{ 
     int loops = *((int *)arg); 
     int l_count, j; 
 
     for (j = 0; j < loops; j++) { 
         pthread_mutex_lock(&mutex); //互斥锁上锁 
      
         l_count = g_count; 
         l_count++; 
         g_count = l_count; 
 
         pthread_mutex_unlock(&mutex);//互斥锁解锁 
     } 
 
     return (void *)0; 
} 
static int loops; 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid1, tid2; 
     int ret; 
 
     /* 获取用户传递的参数  */ 
     if (2 > argc) 
         loops = 10000000;    //没有传递参数默认为1000万次 
     else 
         loops = atoi(argv[1]); 
 
     /* 初始化互斥锁  */ 
     pthread_mutex_init(&mutex, NULL); 
 
     /* 创建2个新线程  */ 
     ret = pthread_create(&tid1, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_create(&tid2, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 等待线程结束  */ 
     ret = pthread_join(tid1, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_join(tid2, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 打印结果  */ 
     printf("g_count = %d\n", g_count); 
     
     exit(0); 
} 

** pthread_mutex_trylock()函数**

  • 当互斥锁已经被其它线程锁住时,调用 pthread_mutex_lock()函数会被阻塞,直到互斥锁解锁;如果线程不希望被阻塞,可以使用 pthread_mutex_trylock()函数
  • 调用 pthread_mutex_trylock()函数尝试对互斥锁进行加锁,如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,如果互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。
/*
@	#include <pthread.h> 
@	参数mutex指向目标互斥锁
@	成功返回 0,失败返回一个非 0值的错误码
@	如果目标互斥锁已经被其它线程锁住,则调用失败返回 EBUSY
*/ 
int pthread_mutex_trylock(pthread_mutex_t *mutex); 

//==========================================================
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_mutex_t mutex; 
static int g_count = 0; 
 
static void *new_thread_start(void *arg) 
{ 
     int loops = *((int *)arg); 
     int l_count, j; 
 
     for (j = 0; j < loops; j++) {
              while(pthread_mutex_trylock(&mutex));    //以非阻塞方式上锁 
 
         l_count = g_count; 
         l_count++; 
         g_count = l_count; 
 
         pthread_mutex_unlock(&mutex);//互斥锁解锁 
     } 
 
     return (void *)0; 
} 
 
static int loops; 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid1, tid2; 
     int ret; 
 
     /* 获取用户传递的参数  */ 
     if (2 > argc) 
         loops = 10000000;    //没有传递参数默认为1000万次 
     else 
         loops = atoi(argv[1]); 
 
     /* 初始化互斥锁  */ 
     pthread_mutex_init(&mutex, NULL); 
 
     /* 创建2个新线程  */ 
     ret = pthread_create(&tid1, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_create(&tid2, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 等待线程结束  */ 
     ret = pthread_join(tid1, NULL); 
     if (ret) {
             fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_join(tid2, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 打印结果  */ 
     printf("g_count = %d\n", g_count); 
     exit(0); 
}

** 销毁互斥锁**

  • 当不再需要互斥锁时,应该将其销毁,通过调用 pthread_mutex_destroy()函数来销毁互斥锁
/*
@	#include <pthread.h> 
@	参数 mutex 指向目标互斥锁
@	同样在调用成功情况下返回 0,失败返回一个非 0值的错误码。 
@	错误情况:
	不能销毁还没有解锁的互斥锁,否则将会出现错误; 
	没有初始化的互斥锁也不能销毁。
@	被 pthread_mutex_destroy()销毁之后的互斥锁,就不能再对它进行上锁和解锁了,需要再次调用pthread_mutex_init()对互斥锁进行初始化之后才能使用。
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex); 

//=================================================================
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_mutex_t mutex; 
static int g_count = 0; 
 
static void *new_thread_start(void *arg) 
{ 
     int loops = *((int *)arg);
  int l_count, j; 
 
     for (j = 0; j < loops; j++) { 
         pthread_mutex_lock(&mutex); //互斥锁上锁 
 
         l_count = g_count; 
         l_count++; 
         g_count = l_count; 
 
         pthread_mutex_unlock(&mutex);//互斥锁解锁 
     } 
 
     return (void *)0; 
} 
 
static int loops; 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid1, tid2; 
     int ret; 
 
     /* 获取用户传递的参数  */ 
     if (2 > argc) 
         loops = 10000000;    //没有传递参数默认为1000万次 
     else 
         loops = atoi(argv[1]); 
 
     /* 初始化互斥锁  */ 
     pthread_mutex_init(&mutex, NULL); 
 
     /* 创建2个新线程  */ 
     ret = pthread_create(&tid1, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_create(&tid2, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     }
   /* 等待线程结束  */ 
     ret = pthread_join(tid1, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_join(tid2, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 打印结果  */ 
     printf("g_count = %d\n", g_count); 
 
     /* 销毁互斥锁  */ 
     pthread_mutex_destroy(&mutex); 
     exit(0); 
}

**互斥锁死锁 **

  • 如果一个线程试图对同一个互斥锁加锁两次,会出现什么情况?情况就是该线程会陷入死锁状态,一直被阻塞永远出不来;这就是出现死锁的一种情况
//  线程A 
pthread_mutex_lock(mutex1); 
pthread_mutex_lock(mutex1); 
  • 一个线程需要同时访问两个或更多不同的共享资源,而每个资源又由不同的互斥锁管理。当超过一个线程对同一组互斥锁(两个或两个以上的互斥锁)进行加锁时,就有可能发生死锁,死锁第二种情况
//  线程A 
pthread_mutex_lock(mutex1); 
pthread_mutex_lock(mutex2); 
 
//  线程B 
pthread_mutex_lock(mutex2); 
pthread_mutex_lock(mutex1); 
  • 要避免此类死锁的问题,最简单的方式就是定义互斥锁的层级关系,当多个线程对一组互斥锁操作时,总是应该按照相同的顺序对该组互锁进行锁定
  • 如果两个线程总是先锁定 mutex1在锁定mutex2,死锁就不会出现
    **互斥锁的属性 **
  • 调用 pthread_mutex_init()函数初始化互斥锁时可以设置互斥锁的属性,通过参数 attr 指定参数 attr 指向一个 pthread_mutexattr_t 类型对象,该对象对互斥锁的属性进行定义,当然,如果将参数 attr设置为 NULL,则表示将互斥锁属性设置为默认值
  • 如果不使用默认属性,在调用 pthread_mutex_init()函数时,参数attr必须要指向一个 pthread_mutexattr_t对象,而不能使用NULL,当定义 pthread_mutexattr_t对象之后,需要使用pthread_mutexattr_init()函数对该对象进行初始化操作,当对象不再使用时,需要使用 pthread_mutexattr_destroy()将其销毁
/*
@	#include <pthread.h>
@	参数 attr 指向需要进行初始化的 pthread_mutexattr_t 对象
@	调用成功返回 0,失败将返回非 0 值的错误码
@	pthread_mutexattr_init()函数将使用默认的互斥锁属性初始化参数 attr 指向的 pthread_mutexattr_t 对象。
@	互斥锁的类型属性控制着互斥锁的锁定特性,一共有 4中类型:
	THREAD_MUTEX_NORMAL: 一种标准的互斥锁类型,不做任何的错误检查或死锁检测
	PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。
	PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁
	PTHREAD_MUTEX_DEFAULT : 此 类 互 斥 锁 提 供 默 认 的 行 为 和 特 性 
*/
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 
int pthread_mutexattr_init(pthread_mutexattr_t *attr); 
  • 可以使用 pthread_mutexattr_gettype()函数得到互斥锁的类型属性,使用pthread_mutexattr_settype()修改/设置互斥锁类型属性
/*
@	#include <pthread.h> 
@	参数 attr 指向 pthread_mutexattr_t 类型对象
@	对于
pthread_mutexattr_gettype()函数,函数调用成功会将互斥锁类型属性保存在参数 type所指向的内存中,通过它返回出来
@	而对于 pthread_mutexattr_settype()函数,会将参数 attr 指向的 pthread_mutexattr_t 对象的类型属性设置为参数 type指定的类型。
*/
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); 
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type


//=========================================================================
pthread_mutex_t mutex; 
pthread_mutexattr_t attr; 
 
/*  初始化互斥锁属性对象  */ 
pthread_mutexattr_init(&attr); 
 
/*  将类型属性设置为PTHREAD_MUTEX_NORMAL */ 
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); 
 
/*  初始化互斥锁  */ 
pthread_mutex_init(&mutex, &attr); 
 
...... 
 
/*  使用完之后  */ 
pthread_mutexattr_destroy(&attr); 
pthread_mutex_destroy(&mutex); 
三、条件变量==线程同步的方法
  • 条件变量是线程可用的另一种同步机制,条件变量用于自动阻塞线程,知道某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的。
    • 一个线程等待某个条件满足而被阻塞;
    • 另一个线程中,条件满足时发出“信号”。
  • 使用条件变量的例子就是生产者和消费者模型
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_mutex_t mutex; 
static int g_avail = 0; 
 
/*  消费者线程  */ 
static void *consumer_thread(void *arg) 
{ 
     for ( ; ; ) { 
         pthread_mutex_lock(&mutex);//上锁 
 
         while (g_avail > 0) 
             g_avail--;       //消费 
 
         pthread_mutex_unlock(&mutex);//解锁 
     } 
 
     return (void *)0; 
} 
 
/*  主线程(生产者)  */ 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid; 
     int ret; 
 
     /* 初始化互斥锁  */ 
     pthread_mutex_init(&mutex, NULL); 
 
     /* 创建新线程  */ 
     ret = pthread_create(&tid, NULL, consumer_thread, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
          exit(-1); 
     } 
     for ( ; ; ) { 
         pthread_mutex_lock(&mutex);//上锁 
         g_avail++;       //生产 
         pthread_mutex_unlock(&mutex);//解锁 
     } 
     exit(0); 
}      
  • 前面说到,条件变量通常搭配互斥锁来使用,是因为条件的检测是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的,线程在改变条件状态之前必须首先锁住互斥锁,不然就可能引发线程不安全的问题。

** 条件变量初始化 **

  • 条件变量使用pthread_cond_t数据类型来表示,类似于互斥锁,在使用条件变量之前必须对其进行初始化。初始化方式同样也有两种,宏PTHREAD_COND_INITIALIZER 或者使用函数 pthread_cond_init()
//宏
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

//函数机制
/*
@	#include <pthread.h> 
@	使用 pthread_cond_init()函数初始化条件变量,
@	当不再使用时,使用 pthread_cond_destroy()销毁条件变量
@	参数 cond指向pthread_cond_t 条件变量对象
@	参数attr指向一个pthread_condattr_t类型对象,pthread_condattr_t数据类
型用于描述条件变量的属性。可将参数 attr设置为NULL,表示使用属性的默认值来初始化条件变量,与使用 PTHREAD_COND_INITIALIZER 宏相同。
@	函数调用成功返回0,失败将返回一个非 0值的错误码。
*/ 
int pthread_cond_destroy(pthread_cond_t *cond); 
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); 
  • 上面需要注意的问题
    • 在使用条件变量之前必须对条件变量进行初始化操作
    • 对已经初始化的条件变量再次进行初始化,将可能会导致未定义行为;
    • 对没有进行初始化的条件变量进行销毁,也将可能会导致未定义行为;
    • 对某个条件变量而言,仅当没有任何线程等待它时,将其销毁才是最安全的
    • 经pthread_cond_destroy()销毁的条件变量, 可以再次调用pthread_cond_init()对其进行重新初始化

通知和等待条件变量

  • 条件变量的主要操作便是发送信号(signal)和等待。发送信号操作即是通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变,这些处于等待状态的线程收到通知之后便会被唤醒,唤醒之后再检查条件是否满足。等待操作是指在收到一个通知前一直处于阻塞状态。
  • 函数 pthread_cond_signal()和 pthread_cond_broadcast()均可向指定的条件变量发送信号,通知一个或多个处于等待状态的线程。调用 pthread_cond_wait()函数是线程阻塞,直到收到条件变量的通知。
  • 在pthread_cond_wait()函数内部会对参数 mutex所指定的互斥锁进行操作,通常情况下,条件判断以及pthread_cond_wait()函数调用均在互斥锁的保护下,也就是说,在此之前线程已经对互斥锁加锁了。调用pthread_cond_wait()函数时,调用者把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当 pthread_cond_wait()被唤醒返回时,会再次锁住互斥锁。
  • 条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。如果调用pthread_cond_signal()和pthread_cond_broadcast()向指定条件变量发送信号时,若无任何线程等待该条件变量,这个信号也就会不了了之
  • 当调用pthread_cond_broadcast()同时唤醒所有线程时,互斥锁也只能被某一线程锁住,其它线程获取锁失败又会陷入阻塞
/*
@	#include <pthread.h>
@	参数 cond 指向目标条件变量,向该条件变量发送信号
@	调用成功返回 0;失败将返回一个非 0值的错误码
@	,pthread_cond_signal()函数至少能唤醒一个线程,而 pthread_cond_broadcast()函数则能唤醒所有线程。使用 pthread_cond_signal()更好,
*/
int pthread_cond_broadcast(pthread_cond_t *cond); 
int pthread_cond_signal(pthread_cond_t *cond); 

/*
@	#include <pthread.h> 
@	cond:指向需要等待的条件变量,目标条件变量
@	mutex:参数 mutex 是一个 pthread_mutex_t 类型指针,指向一个互斥锁对象;条件变量通常是和互斥锁一起使用,
@	返回值:调用成功返回 0;失败将返回一个非0值的错误码。
*/
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 
pthread

//=========================================example
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_mutex_t mutex;    //定义互斥锁 
static pthread_cond_t cond;      //定义条件变量 
static int g_avail = 0;          //全局共享资源 
 
/*  消费者线程  */ 
static void *consumer_thread(void *arg) 
{ 
     for ( ; ; ) { 
         pthread_mutex_lock(&mutex);//上锁 
 
         while (0 >= g_avail) 
             pthread_cond_wait(&cond, &mutex);//等待条件满足 
 
         while (0 < g_avail) 
             g_avail--;       //消费 
 
         pthread_mutex_unlock(&mutex);//解锁 
     } 
 
     return (void *)0; 
} 
 
/*  主线程(生产者)  */ 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid; 
     int ret; 
 
     /* 初始化互斥锁和条件变量  */ 
     pthread_mutex_init(&mutex, NULL); 
     pthread_cond_init(&cond, NULL); 
 
     /* 创建新线程  */ 
     ret = pthread_create(&tid, NULL, consumer_thread, NULL); 
          if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     for ( ; ; ) { 
         pthread_mutex_lock(&mutex);//上锁 
         g_avail++;       //生产 
         pthread_mutex_unlock(&mutex);//解锁 
         pthread_cond_signal(&cond);//向条件变量发送信号 
     } 
 
     exit(0); 
}

**条件变量的判断条件 **

  • 使用条件变量,都会有与之相关的判断条件,通常情况下,会涉及到一个或多个共享变量
  • 必须使用while循环,而不是if语句,这是一种通用的设计原则:当线程从pthread_cond_wait()返回时,并不能确定判断条件的状态,应该立即重新检查判断条件,如果条件不满足,那就继续休眠等待,如下图所示
    在这里插入图片描述
    条件变量的属性
  • 调用pthread_cond_init()函数初始化条件变量时,可以设置条件变量的属性,通过参数attr指定,参数 attr指向一个pthread_condattr_t类型对象,该对象对条件变量的属性进行定义,当然,如果将参数attr设置为NULL,表示使用默认值来初始化条件变量属性
四、自旋锁
  • 如果在获取自旋锁时,自旋锁处于未锁定状态,那么将立即获得锁(对自旋锁上锁);如果在获取自旋锁时,自旋锁已经处于锁定状态了,那么获取锁操作将会在原地“自旋” , 直到该自旋锁的持有者释放了锁
  • 自旋锁和互斥锁的区别:
    • 实现方式上的区别:互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
    • 开销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低,故自旋锁不适用于等待时间比较长的情况。
    • 使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!

**自旋锁初始化 **

  • 自旋锁使用 pthread_spinlock_t 数据类型表示,
  • 当定义自旋锁后,需要使用 pthread_spin_init()函数对其进行初始化,当不再使用自旋锁时,调用 pthread_spin_destroy()函数将其销毁
/*
@	#include <pthread.h> 
@	参数lock指向了需要进行初始化或销毁的自旋锁对象,
@	参数 pshared表示自旋锁的进程共享属性
	PTHREAD_PROCESS_SHARED:共享自旋锁,该自旋锁可以在多个进程中的线程之间共享; 
	PTHREAD_PROCESS_PRIVATE:私有自旋锁,只有本进程内的线程才能够使用该自旋锁
@	这两个函数在调用成功的情况下返回0;失败将返回一个非 0值的错误码
*/

int pthread_spin_destroy(pthread_spinlock_t *lock); 
int pthread_spin_init(pthread_spinlock_t *lock, int pshared); 

** 自旋锁加锁和解锁**

  • 可以使用pthread_spin_lock()函数或pthread_spin_trylock()函数对自旋锁进行加锁,前者在未获取到锁时一直“自旋”;对于后者,如果未能获取到锁,就立刻返回错误,错误码为EBUSY。
  • 不管以何种方式加锁,自旋锁都可以使用 pthread_spin_unlock()函数对自旋锁进行解锁。
  • 如果自旋锁处于未锁定状态,调用 pthread_spin_lock()会将其锁定(上锁),如果其它线程已经将自旋锁锁住了,那本次调用将会“自旋”等待;如果试图对同一自旋锁加锁两次必然会导致死锁。
/*
@	#include <pthread.h> 
@	参数lock指向自旋锁对象,
@	调用成功返回 0,失败将返回一个非 0值的错误码
*/

int pthread_spin_lock(pthread_spinlock_t *lock); 
int pthread_spin_trylock(pthread_spinlock_t *lock); 
int pthread_spin_unlock(pthread_spinlock_t *lock); 


//=============================================使用自旋锁实现线程的同步
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_spinlock_t spin;//定义自旋锁 
static int g_count = 0; 
 
static void *new_thread_start(void *arg) 
{ 
     int loops = *((int *)arg); 
     int l_count, j; 
 
     for (j = 0; j < loops; j++) { 
         pthread_spin_lock(&spin); //自旋锁上锁 
      
         l_count = g_count; 
         l_count++; 
         g_count = l_count; 
 
         pthread_spin_unlock(&spin);//自旋锁解锁 
     } 
 
     return (void *)0; 
} 
tatic int loops; 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid1, tid2; 
     int ret; 
 
     /* 获取用户传递的参数  */ 
     if (2 > argc) 
         loops = 10000000;    //没有传递参数默认为1000万次 
     else 
         loops = atoi(argv[1]); 
 
     /* 初始化自旋锁(私有) */ 
     pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE); 
 
     /* 创建2个新线程  */ 
     ret = pthread_create(&tid1, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_create(&tid2, NULL, new_thread_start, &loops); 
     if (ret) { 
         fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 等待线程结束  */ 
     ret = pthread_join(tid1, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     ret = pthread_join(tid2, NULL); 
     if (ret) { 
         fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); 
         exit(-1); 
     } 
 
     /* 打印结果  */ 
     printf("g_count = %d\n", g_count); 
 
     /* 销毁自旋锁  */ 
     pthread_spin_destroy(&spin); 
     exit(0); 
} 
五、读写锁
  • 互斥锁或自旋锁要么是加锁状态、要么是不加锁状态,而且一次只有一个线程可以对其加锁。
  • 读写锁有3 种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和不加锁状态。
    在这里插入图片描述
  • 一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。因此可知,读写锁比互斥锁具有更高的并行性
  • 读写锁有下面两个规则:
    • 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞
    • 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。
  • 读写锁非常适合于对共享数据读的次数远大于写的次数的情况
  • 当读写锁处于写模式加锁状态时,它所保护的数据可以被安全的修改,因为一次只有一个线程可以在写模式下拥有这个锁;当读写锁处于读模式加锁状态时,它所保护的数据就可以被多个获取读模式锁的线程读取。所以在应用程序当中,使用读写锁实现线程同步,当线程需要对共享数据进行读操作时,需要先获取读模式锁(对读模式锁进行加锁),当读取操作完成之后再释放读模式锁(对读模式锁进行解锁);当线程需要对共享数据进行写操作时,需要先获取到写模式锁,当写操作完成之后再释放写模式锁
  • 读写锁也叫做共享互斥锁。当读写锁是读模式锁住时,就可以说成是共享模式锁住。当它是写模式锁住时,就可以说成是互斥模式锁住

读写锁初始化

  • 与互斥锁、自旋锁类似,在使用读写锁之前也必须对读写锁进行初始化操作,读写锁使用pthread_rwlock_t数据类型表示
  • 读写锁的初始化可以使用宏 PTHREAD_RWLOCK_INITIALIZER 或者函数
    pthread_rwlock_init(),其初始化方式与互斥锁相同
  • 对于其它方式可以使用 pthread_rwlock_init()函数对其进行初始化,当读写锁不再使用时,需要调用pthread_rwlock_destroy()函数将其销毁,
//宏
//使用宏进行初始化是不进行错误检查的
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; 

//函数
/*
@	#include <pthread.h> 
@	参数 rwlock 指向需要进行初始化或销毁的读写锁对象
@	对于 pthread_rwlock_init()函数,参数 attr 是一个 pthread_rwlockattr_t *类型指针,指向 pthread_rwlockattr_t对象
@	。pthread_rwlockattr_t数据类型定义了读写锁的属性
@	调用成功返回 0,失败将返回一个非 0 值的错误码
*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

//========================example
pthread_rwlock_t rwlock; 
pthread_rwlock_init(&rwlock, NULL); 
...... 
pthread_rwlock_destroy(&rwlock); 

读写锁上锁和解锁

  • 以读模式对读写锁进行上锁,需要调用 pthread_rwlock_rdlock()函数,以写模式对读写锁进行上锁,需要调用pthread_rwlock_wrlock()函数。不管是以何种方式锁住读写锁,均可以调用 pthread_rwlock_unlock()函数解锁
  • 当读写锁处于写模式加锁状态时,其它线程调用pthread_rwlock_rdlock()或 pthread_rwlock_wrlock()函数均会获取锁失败,从而陷入阻塞等待状态;当读写锁处于读模式加锁状态时,其它线程调用pthread_rwlock_rdlock()函数可以成功获取到锁,如果调用 pthread_rwlock_wrlock()函数则不能获取到锁,从而陷入阻塞等待状态。
  • 如果线程不希望被阻塞,可以调用pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock()来尝试加锁,如果不可以获取锁时。这两个函数都会立马返回错误,错误码为EBUSY
/*
@	#include <pthread.h> 
@	参数rwlock 指向读写锁对象
@	调用成功返回 0,失败返回一个非 0值的错误码
*/ 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 

//==========================================================

/*
@	#include <pthread.h> 
@	参数rwlock指向需要加锁的读写锁
@	加锁成功返回0,加锁失败则返回EBUSY
*/ 
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 	
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 


//=====================================使用读写锁实现线程同步
#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <string.h> 
 
static pthread_rwlock_t rwlock;//定义读写锁 
static int g_count = 0; 
 
static void *read_thread(void *arg) 
{ 
     int number = *((int *)arg); 
     int j; 
 
     for (j = 0; j < 10; j++) { 
         pthread_rwlock_rdlock(&rwlock); //以读模式获取锁 
         printf("读线程<%d>, g_count=%d\n", number+1, g_count); 
         pthread_rwlock_unlock(&rwlock);//解锁 
         sleep(1); 
     } 
     return (void *)0; 
} 
 
static void *write_thread(void *arg) 
{ 
     int number = *((int *)arg); 
     int j; 
 
     for (j = 0; j < 10; j++) { 
         pthread_rwlock_wrlock(&rwlock); //以写模式获取锁 
         printf("写线程<%d>, g_count=%d\n", number+1, g_count+=20); 
         pthread_rwlock_unlock(&rwlock);//解锁 
         sleep(1); 
     } 
 
     return (void *)0; 
} 
 
static int nums[5] = {0, 1, 2, 3, 4}; 
int main(int argc, char *argv[]) 
{ 
     pthread_t tid[10]; 
     int j; 
 
     /* 对读写锁进行初始化  */ 
     pthread_rwlock_init(&rwlock, NULL); 
 
     /* 创建5个读 g_count变量的线程  */ 
     for (j = 0; j < 5; j++) 
         pthread_create(&tid[j], NULL, read_thread, &nums[j]); 
 
     /* 创建5个写 g_count变量的线程  */ 
     for (j = 0; j < 5; j++) 
         pthread_create(&tid[j+5], NULL, write_thread, &nums[j]); 
 
     /* 等待线程结束  */ 
     for (j = 0; j < 10; j++) 
         pthread_join(tid[j], NULL);//回收线程 
 
     /* 销毁自旋锁  */ 
     pthread_rwlock_destroy(&rwlock); 
     exit(0); 
} 

读写锁的属性

  • 读写锁与互斥锁类似,也是有属性的,读写锁的属性使用 pthread_rwlockattr_t数据类型来表示,当定义pthread_rwlockattr_t 对象时,需要使用 pthread_rwlockattr_init()函数对其进行初始化操作,初始化会将pthread_rwlockattr_t 对象定义的各个读写锁属性初始化为默认值;当不再使用 pthread_rwlockattr_t 对象时,需要调用 pthread_rwlockattr_destroy()函数将其销毁
/*
@	#include <pthread.h> 
@	参数attr指向需要进行初始化或销毁的 pthread_rwlockattr_t对象
@	函数调用成功返回 0,失败将返回一个非 0值的错误码。
*/
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr); 
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr); 
  • 读写锁只有一个属性,那便是进程共享属性,它与互斥锁以及自旋锁的进程共享属性相同。函数 pthread_rwlockattr_getpshared()用于从pthread_rwlockattr_t对象中获取共享属性,函数pthread_rwlockattr_setpshared()用于设置 pthread_rwlockattr_t
    对象中的共享属性,
/*
@ 	#include <pthread.h> 
@	attr:指向pthread_rwlockattr_t对象; 
@	pshared: 调用pthread_rwlockattr_getpshared()获取共享属性,将其保存在参数 pshared所指向的内存中
@	返回值:成功返回0,失败将返回一个非 0值的错误码。
*/
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared); 

/*
@	#include <pthread.h> 
@	attr:指向pthread_rwlockattr_t对象;
@	pshared:调用 pthread_rwlockattr_setpshared()设置读写锁的共享属性,将其设置为参数 pshared 指定的值。参数pshared可取值如下:
	PTHREAD_PROCESS_SHARED:共享读写锁。该读写锁可以在多个进程中的线程之间共享;
	PTHREAD_PROCESS_PRIVATE:私有读写锁。只有本进程内的线程才能够使用该读写锁,这是读写锁共享属性的默认值。
@	返回值:调用成功的情况下返回 0;失败将返回一个非 0值的错误码。 
*/
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared); 

//==============================================example
pthread_rwlock_t rwlock;  //定义读写锁 
pthread_rwlockattr_t attr;  //定义读写锁属性 
 
/*  初始化读写锁属性对象  */ 
pthread_rwlockattr_init(&attr); 
 
/*  将进程共享属性设置为PTHREAD_PROCESS_PRIVATE */ 
pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); 
 
/*  初始化读写锁  */ 
pthread_rwlock_init(&rwlock, &attr); 
 
...... 
 
/*  使用完之后  */ 
pthread_rwlock_destroy(&rwlock);  //销毁读写锁 
pthread_rwlockattr_destroy(&attr);  //销毁读写锁属性对象
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值