一. 互斥锁

1. 使用步骤

  1. 创建互斥锁

    pthread_mutex_t mutex;
    
  2. 初始化互斥锁

    pthread_mutex_init(&mutex); 相当于mutex=1;
    
  3. 在临界区添加互斥锁

    pthread_mutex_lock(&mutex);   mutex=0
    [临界区代码]
    pthread_mutex_unlock(&mutex);    mutex=1
    
  4. 释放互斥锁资源

    pthread_mutex_destroy(&mutex);
    

必须在所有操作共享资源的线程上都加上锁,否则不能起线程同步的作用

2. 例

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <pthread.h>
  #include <time.h>

  //定义一把锁
  pthread_mutex_t mutex;

  void *mythread1(void *args)
  {
  	while(1)
  	{
  		//加锁
      	pthread_mutex_lock(&mutex);

      	printf("hello ");
      	sleep(rand()%3);
      	printf("world\n");
      
  		//解锁
  		pthread_mutex_unlock(&mutex);
  		sleep(rand()%3);
  	}
  
  	pthread_exit(NULL);
  }


  void *mythread2(void *args)
  {
  	while(1)
  	{
  		//加锁
      	pthread_mutex_lock(&mutex);

      	printf("HELLO ");
      	sleep(rand()%3);
      	printf("WORLD\n");

  		//解锁
  		pthread_mutex_unlock(&mutex);
  		sleep(rand()%3);
  	}

  	pthread_exit(NULL);
  }

  int main()
  {
  	int ret;
  	pthread_t thread1;
  	pthread_t thread2;

  	//随机数种子
  	srand(time(NULL));

  	//互斥锁初始化
  	pthread_mutex_init(&mutex, NULL);

  	ret = pthread_create(&thread1, NULL, mythread1, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	ret = pthread_create(&thread2, NULL, mythread2, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	//等待线程结束
  	pthread_join(thread1, NULL);
  	pthread_join(thread2, NULL);

  	//释放互斥锁
  	pthread_mutex_destroy(&mutex);
  	return 0;
  }

二. 读写锁

1. 什么是读写锁?

读写锁也叫共享-独占锁,当以读模式锁住时,他是以共享模式锁住的,以写模式锁住时,他是以独占模式锁住的。即读共享,写独占。

2. 读写锁使用场景

读写锁适用于对数据结构读的次数远大于写的情况

3. 读写锁的特性

  • 读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。
  • 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  • 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高。

4. 例

  • 线程A加写锁成功,线程B请求读锁。
    • 线程B阻塞
  • 线程A持有读锁,线程B请求写锁。
    • 线程B阻塞
  • 线程A拥有读锁,线程B请求读锁
    • 线程B加读锁成功
  • 线程A持有读锁,然后线程B请求读锁,线程C请求写锁
    • 线程B、C阻塞, 写锁优先级高
    • 线程A解锁,C写锁加锁成功,C阻塞
    • 线程B解锁,C加读锁成功。

读并行,写独占,写的优先级高

5. 读写锁的主要操作函数

  • 定义一把读写锁

     pthread_rwlock_t rwlock;
    
  • 初始化读写锁

     int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock , const pthread_rwlockattr_t *restrict attr);
    函数参数:
       * rwlock:读写锁       
       * attr:读写锁属性,传NULL为默认属性
    
  • 销毁读写锁

     int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
  • 加读锁

     int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    
  • 尝试加读锁

     int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
  • 加写锁

     int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    
  • 尝试加写锁

     int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    
  • 解锁

     int pthread_rwlock_unlock(&pthread_rwlock_t *rwlock);
    

6. 例

3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。

  //读写锁测试程序
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <pthread.h>

  int number = 0;

  //定义一把读写锁
  pthread_rwlock_t rwlock;

  //写线程回调函数
  void *thread_write(void *arg)
  {
  	int i = *(int *)arg;

  	int cur;

  	while(1)
  	{
	//加写锁
  		pthread_rwlock_wrlock(&rwlock);

  		cur = number;
      	cur++;
      	number = cur;	
      	printf("[%d]-W:[%d]\n", i, cur);

      	//解锁
      	pthread_rwlock_unlock(&rwlock);
      	sleep(rand()%3);
  	}
  }

  //读线程回调函数
  void *thread_read(void *arg)
  {
  	int i = *(int *)arg;
  	int cur;

  	while(1)
  	{
  		//加读锁
  		pthread_rwlock_rdlock(&rwlock);

  		cur = number;
  		printf("[%d]-R:[%d]\n", i, cur);

      	//解锁
      	pthread_rwlock_unlock(&rwlock);
      	sleep(rand()%3);
  	}	
  }

  int main()
  {
  	int n = 8;
  	int i = 0;
  	int arr[8];
  	pthread_t thread[8];

  	//读写锁初始化
  	pthread_rwlock_init(&rwlock, NULL);

  	//创建3个写子线程
  	for(i=0; i<3; i++)
  	{
  		arr[i] = i;
  		pthread_create(&thread[i], NULL, thread_write, &arr[i]);
  	}

  	//创建5个读子线程
  	for(i=3; i<n; i++)
  	{
  		arr[i] = i;
  		pthread_create(&thread[i], NULL, thread_read, &arr[i]);
  	}

  	//回收子线程
  	int j = 0;
  	for(j=0;j<n; j++)
  	{
  		pthread_join(thread[j], NULL);
  	}

  	//释放锁
  	pthread_rwlock_destroy(&rwlock);

  	return 0;
  }

三. 条件变量

1. 条件变量不是锁,但它也可以造成线程阻塞。通常与互斥锁配合使用,给多线程提供一个会和的场所。

  • 使用互斥量保护共享数据
  • 使用条件变量可以使线程阻塞,等待某个条件的发生,当条件满足时会解除阻塞

2. 条件变量的两个动作

  • 条件不满足,阻塞线程
  • 条件满足,通知阻塞的线程接触阻塞,开始工作

3. 条件变量相关函数

  • 定义条件变量

     pthread_cond_t cond;
    
  • 初始化条件变量

     int pthread_cond_init(pthread_cond_t *restrict cond , const pthread_condattr_t *restrict attr);
    函数参数:
    cond:条件变量
    attr:条件变量属性,通常传NULL
    函数返回值:成功返回0,失败返回错误号
    
  • 销毁条件变量

     int pthread_cond_destroy(pthread_cond_t *cond);
    成功返回0,失败返回错误号
    
  • 阻塞线程或解除阻塞

     int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    条件不满足,引起线程阻塞并加锁,条件满足,解除线程阻塞并加锁
    函数参数:
    cond:条件变量
    mutex:互斥锁变量
    成功返回0,失败返回错误号
    
  • 唤醒至少一个阻塞在该条件变量上的线程

     int pthread_cond_signal(pthread_cond_t *cond);
    成功返回0,失败返回错误号
    

4. 例

  //使用条件变量实现生产者和消费者模型
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <pthread.h>
  typedef struct node
  {
  	int data;
  	struct node *next;
  }NODE;

  NODE *head = NULL;

  //定义一把锁
  pthread_mutex_t mutex;

  //定义条件变量
  pthread_cond_t cond;

  //生产者线程
  void *producer(void *arg)
  {
  	NODE *pNode = NULL;
  	while(1)
  	{
      	//生产一个节点
      	pNode = (NODE *)malloc(sizeof(NODE));
      	if(pNode==NULL)
      	{
      		perror("malloc error");
      		exit(-1);
      	}
      	pNode->data = rand()%1000;
      	printf("P:[%d]\n", pNode->data);

      	//加锁
      	pthread_mutex_lock(&mutex);

      	pNode->next = head;
      	head = pNode;

  		//解锁
  		pthread_mutex_unlock(&mutex);

      	//通知消费者线程解除阻塞
      	pthread_cond_signal(&cond);
	
  		sleep(rand()%3);
  	}
  }


  //消费者线程
  void *consumer(void *arg)
  {
  	NODE *pNode = NULL;
  	while(1)
  	{
  		//加锁
          pthread_mutex_lock(&mutex);
	
      	if(head==NULL)
      	{
	      	//若条件不满足,需要阻塞等待
      		//若条件不满足,则阻塞等待并解锁;
  			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
  			pthread_cond_wait(&cond, &mutex);
  		}

  		printf("C:[%d]\n", head->data);	
  		pNode = head;
  		head = head->next;

  		//解锁
  		pthread_mutex_unlock(&mutex);

  		free(pNode);
  		pNode = NULL;

  		sleep(rand()%3);
  	}
  }

  int main()
  {
  	int ret;
  	pthread_t thread1;
  	pthread_t thread2;

  	//初始化互斥锁
  	pthread_mutex_init(&mutex, NULL);

  	//条件变量初始化
  	pthread_cond_init(&cond, NULL);

  	//创建生产者线程
  	ret = pthread_create(&thread1, NULL, producer, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	//创建消费者线程
  	ret = pthread_create(&thread2, NULL, consumer, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	//等待线程结束
  	pthread_join(thread1, NULL);
  	pthread_join(thread2, NULL);

  	//释放互斥锁
  	pthread_mutex_destroy(&mutex);

  	//释放条件变量
  	pthread_cond_destroy(&cond);

  	return 0;
  }

四. 信号量

1. 信号量

信号量相当于多把锁,可以理解为是加强版的互斥锁

2. 相关函数

  • 定义信号量

     sem_t sem;
    
  • 初始化信号量

     int sem_init(sem_t *sem , int pshared , unsigned int value);
    函数参数
    sem:信号量变量
    pshared:0表示线程同步,1表示进程同步
    value:最多有几个线程操作共享数据
    成功返回0,失败返回-1,并设置errno值
    
  • int sem_wait(sem_t *sem);
    调用该函数一次,相当于sem--,当sem为0时,引起阻塞。成功返回0,失败返回-1,并设置errno值

  • int sem_post(sem_t *sem);
    调用一次相当于sem++。成功返回0,失败返回-1,并设置errno值

  • int sem_trywait(sem_t *sem);
    尝试加锁,若失败直接返回,不阻塞。成功返回0,失败返回-1,并设置errno值

  • int sem_destroy(sem_t *sem);
    销毁信号量。成功返回0,失败返回-1,并设置errno值

3. 例

  //使用信号量实现生产者和消费者模型
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <pthread.h>
  #include <semaphore.h>
  typedef struct node
  {
  	int data;
  	struct node *next;
  }NODE;

  NODE *head = NULL;

  //定义信号量
  sem_t sem_producer;
  sem_t sem_consumer;

  //生产者线程
  void *producer(void *arg)
  {
  	NODE *pNode = NULL;
  	while(1)
  	{
  		//生产一个节点
  		pNode = (NODE *)malloc(sizeof(NODE));
  		if(pNode==NULL)
  		{
  			perror("malloc error");
      		exit(-1);
  	      	}
  		pNode->data = rand()%1000;
            printf("P:[%d]\n", pNode->data);

  		//加锁
  		sem_wait(&sem_producer); //--

  		pNode->next = head;
  		head = pNode;

  		//解锁
  		sem_post(&sem_consumer);  //相当于++

  		sleep(rand()%3);
  	}
  }


  //消费者线程
  void *consumer(void *arg)
  {
  	NODE *pNode = NULL;
  	while(1)
  	{
  		//加锁
  		sem_wait(&sem_consumer); //相当于--
	
  		printf("C:[%d]\n", head->data);	
  		pNode = head;
  		head = head->next;

  		//解锁
      	sem_post(&sem_producer); //相当于++

      	free(pNode);
  		pNode = NULL;

  		sleep(rand()%3);
  	}
  }

  int main()
  {
  	int ret;
  	pthread_t thread1;
  	pthread_t thread2;

  	//初始化信号量
  	sem_init(&sem_producer, 0, 5);
  	sem_init(&sem_consumer, 0, 0);

  	//创建生产者线程
  	ret = pthread_create(&thread1, NULL, producer, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	//创建消费者线程
  	ret = pthread_create(&thread2, NULL, consumer, NULL);
  	if(ret!=0)
  	{
  		printf("pthread_create error, [%s]\n", strerror(ret));
  		return -1;
  	}

  	//等待线程结束
  	pthread_join(thread1, NULL);
  	pthread_join(thread2, NULL);

  	//释放信号量资源
  	sem_destroy(&sem_producer);
  	sem_destroy(&sem_consumer);

  	return 0;
  }