一. 互斥锁

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;
}