目录
一. 互斥锁
1. 使用步骤
-
创建互斥锁
pthread_mutex_t mutex;
-
初始化互斥锁
pthread_mutex_init(&mutex); 相当于mutex=1;
-
在临界区添加互斥锁
pthread_mutex_lock(&mutex); mutex=0 [临界区代码] pthread_mutex_unlock(&mutex); mutex=1
-
释放互斥锁资源
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;
}