一、互斥锁。
1、什么是互斥锁?特点如何?
互斥锁是专门用于处理线程之间互斥关系的一种方式,它有两种状态:上锁状态/解锁状态
如果互斥锁处于上锁状态,那么再上锁就会阻塞到这把锁解开为止,才能上锁。
解锁状态下依然可以解锁,不会阻塞。
2、关于互斥锁的函数接口。
1)定义互斥锁变量。 数据类型: pthread_mutex_t
pthread_mutex_t m;
2)如何初始化互斥锁? --> pthread_mutex_init() --> man 3 pthread_mutex_init
静态初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; (把宏赋值给互斥锁变量,就已经完成了初始化了)
动态初始化:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:
mutex:互斥锁变量的地址
attr:普通属性设置为NULL
返回值:
成功:0
失败:非0
3)上锁。 --> pthread_mutex_lock() --> man 3 pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量的地址
返回值:
成功:0
失败:非0
4)解锁。 --> pthread_mutex_unlock() --> man 3 pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量的地址
返回值:
成功:0
失败:非0
5)销毁互斥锁。 --> pthread_mutex_destroy() --> man 3 pthread_mutex_destroy
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量的地址
返回值:
成功:0
失败:非0
什么情况下会使用互斥锁?
当多个线程使用同一个资源时,为了防止多个线程一起访问该资源,我们一般会这么做:
上锁
.....
..... -> 开始做任务
.....
解锁
练习1: 使用互斥锁去完成昨晚作业的第2题。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; //静态初始化
void *func(void *arg)
{
//上锁
pthread_mutex_lock(&m);
//做任务
char str[] = "helloworld";
int i;
for(i=0;str[i]!='\0';i++)
{
fprintf(stderr,"%c",str[i]);
sleep(1);
}
//解锁
pthread_mutex_unlock(&m);
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
int i;
pthread_t tid[5];
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,func,NULL);
}
for(i=0;i<5;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&m);
return 0;
}
二、读写锁。
1、互斥锁有什么缺陷?
互斥锁无论是读操作还是写操作,都是要先上锁,那么这样效率就会非常低,因为读取数据就没必要分开读,可以一起读。
最好办法: 访问资源可以一起访问,但是修改资源就不可以一起修改。
访问资源(一起读一本书) --> 同时上读锁。 --> 读锁是一把共享锁
修改资源(一起做一份试卷) --> 不能同时上写锁。 --> 写锁是一把互斥锁
这把既有读锁,又有写锁的锁,我们称之为读写锁。
2、关于读写锁的函数接口。
1)定义一个读写锁变量 数据类型: pthread_rwlock_t
pthread_rwlock_t rwlock;
2)初始化读写锁? --> pthread_rwlock_init() --> man 3 pthread_rwlock_init
静态初始化:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
动态初始化:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
参数:
rwlock: 读写锁的变量的地址
attr:属性设置为NULL即可。
返回值:
成功:0
失败:非0
3)上读锁。 --> pthread_rwlock_rdlock() --> man 3 pthread_rwlock_rdlock
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
参数:
rwlock:读写锁的变量的地址
返回值:
成功:0
失败:非0
4)上写锁。 --> pthread_rwlock_wrlock() --> man 3 pthread_rwlock_wrlock
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
参数:
rwlock:读写锁的变量的地址
返回值:
成功:0
失败:非0
5)解锁。 --> pthread_rwlock_unlock() -> man 3 pthread_rwlock_unlock
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数:
rwlock:读写锁的变量的地址
返回值:
成功:0
失败:非0
6)销毁读写锁 --> pthread_rwlock_destroy() -> man 3 pthread_rwlock_destroy
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
rwlock:读写锁的变量的地址
返回值:
成功:0
失败:非0
练习2: 临时资源: "int a = 100" --> 全局变量
现在有4个线程,有两个线程想打印a的值 --> 读 t1:3s t2:5s
有两个线程想修改a的值 一个想修改为100 一个想修改为50
t3:4s t4:6s
验证: 1)读锁可以同时上,但是写锁不能同时上。
2)读锁与写锁能不能同时上? --> 不可能
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int a = 100; //临界资源
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //读写锁
//读
void *func1(void *arg)
{
//上读锁
pthread_rwlock_rdlock(&rwlock);
printf("thread1 read lock!\n");
printf("a = %d\n",a); //打印a的值
sleep(3);
//解锁
pthread_rwlock_unlock(&rwlock);
printf("thread1 read unlock!\n");
}
//读
void *func2(void *arg)
{
//上读锁
pthread_rwlock_rdlock(&rwlock);
printf("thread2 read lock!\n");
printf("a = %d\n",a); //打印a的值
sleep(5);
//解锁
pthread_rwlock_unlock(&rwlock);
printf("thread2 read unlock!\n");
}
//写
void *func3(void *arg)
{
//上写锁
pthread_rwlock_wrlock(&rwlock);
printf("thread3 write lock!\n");
a = 100;
sleep(4);
//解锁
pthread_rwlock_unlock(&rwlock);
printf("thread3 write unlock!\n");
}
//写
void *func4(void *arg)
{
//上写锁
pthread_rwlock_wrlock(&rwlock);
printf("thread4 write lock!\n");
a = 50;
sleep(6);
//解锁
pthread_rwlock_unlock(&rwlock);
printf("thread4 write unlock!\n");
}
void *func_time(void *arg)
{
int i;
for(i=0;i<100;i++)
{
printf("i = %d\n",i);
sleep(1);
}
}
int main(int argc,char *argv[])
{
//0. 创建一个线程,用于计算时间的流逝
pthread_t time_test;
pthread_create(&time_test,NULL,func_time,NULL);
//1. 创建4个线程
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,func1,NULL);
pthread_create(&tid2,NULL,func2,NULL);
pthread_create(&tid3,NULL,func3,NULL);
pthread_create(&tid4,NULL,func4,NULL);
//2. 接合所有的线程
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
//3. 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
三、条件变量。
1、什么是条件变量?
线程因为某一个条件/情况不成立下,就会进入一个变量中等待,这个存储线程的变量就是条件变量。
条件变量一定要与互斥锁连用。
2、关于条件变量的函数接口。
1)定义一个条件变量。 --> 数据类型: pthread_cond_t
pthread_cond_t v;
2)初始化条件变量。 --> pthread_cond_init() --> man 3 pthread_cond_init
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:
cond:条件变量的地址
attr:普通属性设置为NULL
返回值:
成功:0
失败:非0
3)如何进入条件变量中等待? ---> pthread_cond_wait() -> man 3 pthread_cond_wait
进入条件变量时,会自动解锁。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
cond:条件变量的地址
mutex:互斥锁的地址
返回值:
成功:0
失败:非0
4)如何唤醒条件变量中的线程?
离开条件变量时,会自动上锁。
广播:唤醒所有在条件变量中的线程。 --> pthread_cond_broadcast()
单播:随机唤醒条件变量中其中一条线程。 --> pthread_cond_signal()
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数:
cond:条件变量的地址
返回值:
成功:0
失败:非0
5)如何销毁条件变量? --> pthread_cond_destroy() --> man 3 pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
cond:条件变量的地址
返回值:
成功:0
失败:非0
练习4: 有5个线程,每一个线程任务都是拿200块。先在银行卡(全局变量sum)中存400块,2个线程拿钱退出了,3个进入条件变量中睡眠。
主线程5s后再打400块到银行卡,唤醒所有的小孩起来拿钱,4个线程退出了,1个还在条件变量睡眠,3s后再打200块,然后唤醒一个线程起来拿钱
5个线程都可以拿到钱退出。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int sum = 400;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; //互斥锁静态初始化
pthread_cond_t v = PTHREAD_COND_INITIALIZER; //条件变量静态初始化
//每一个子线程任务:都是从银行卡中取200块。
void *func(void *arg)
{
//1. 访问银行卡之前,都要上锁
pthread_mutex_lock(&m);
//2. 判断银行卡中是否<200块
while(sum < 200)
{
//3. 如果<200,那么就进入条件变量中等待
pthread_cond_wait(&v,&m);
}
//4. 代码能运行到这里,sum肯定是>=200
//5. 拿钱
printf("before money:%d\n",sum);
sum -= 200;
printf("after money:%d\n",sum);
//6. 解锁
pthread_mutex_unlock(&m);
//7. 走人
pthread_exit(NULL);
}
void *time_func(void *arg)
{
int i;
for(i=0;i<100;i++)
{
printf("i = %d\n",i);
sleep(1);
}
}
int main(int argc,char *argv[])
{
//0. 创建一个线程,用于计算时间的流逝
pthread_t tid_test;
pthread_create(&tid_test,NULL,time_func,NULL);
//1. 创建5个线程
int i;
pthread_t tid[5];
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,func,NULL);
}
//2. 往银行卡中打钱
sleep(5);
pthread_mutex_lock(&m);
sum += 400;
printf("main thread + 400!\n");
pthread_mutex_unlock(&m);
//3. 唤醒所有的线程
sleep(2);
pthread_cond_broadcast(&v);
sleep(3);
//4. 再打200块
pthread_mutex_lock(&m);
sum += 200;
printf("main thread + 200!\n");
pthread_mutex_unlock(&m);
//5. 唤醒一个小孩
sleep(2);
pthread_cond_signal(&v);
//6. 接合所有的线程
for(i=0;i<5;i++)
{
pthread_join(tid[i],NULL);
}
//7. 销毁互斥锁与条件变量
pthread_mutex_destroy(&m);
pthread_cond_destroy(&v);
return 0;
}