01 学习目标
1.熟练掌握互斥锁的使用
2.说出什么叫死锁以及解决方案
3.熟练掌握读写锁的使用
4.熟练掌握条件变量的使用
5.理解条件变量实现的生产消费者模型
6.理解信号量实现的生产消费者模型
02 线程同步的概念
同步的概念:如文件同步,指让两个或多个文件夹里的文件保持一致,等等。
但线程同步不一致。
同步即协同步调,按预定的先后次序运行。
线程同步:指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程为保证数据一致性,不能调用该功能。
解决同步问题:协调步骤,顺序执行。
解决同步的问题:加锁!
数据混乱的原因:
- 资源共享(独享资源则不会)
- 调度随机(意味着数据访问会出现竞争)
- 线程间缺乏必要的同步机制
只能解决第三点。
03 互斥量的使用
两个线程访问同一块共享资源,如果不协调顺序,容易造成数据混乱。
加锁
mutex
pthread_mutex_intit 初始化
pthread_mutex_destroy 摧毁
pthread_mutex_lock 加锁
pthread_mutex_unlock(pthread_mutex_t * mutex) 解锁
互斥量的使用步骤:
- 初始化
- 加锁
- 执行逻辑–操作共享数据
- 解锁
注意事项:
加锁需要最小粒度,不要一直占用临界区。
pthread_print.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
pthtread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int sum=0;
void *thr1(void *arg)
{
while(1)
{
//先上锁
pthread_mutex_lock(&mutex);//加锁当有线程已经加速的时候会阻塞
printf("hello");
sleep(rand()%3);
printf("world\n");
//释放锁
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
void *thr2(void *arg)
{
while(1)
{
//先上锁
pthread_mutex_lock(&mutex);//加锁当有线程已经加速的时候会阻塞
printf("HELLO");
sleep(rand()%3);
printf("WORLD\n");
//释放锁
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
int main()
{
pthread_t tid[2];
pthread_create(&tid[0],NULL,thr1,NULL);
pthread_create(&tid[1],NULL,thr2,NULL);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
输出效果:要么全大写,要么全小写。
尝试加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex_trylock.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
pthtread_mutex_t mutex ;
void *thr(void *arg)
{
while(1)
{
//先上锁
pthread_mutex_lock(&mutex);//加锁当有线程已经加速的时候会阻塞
printf("hello world\n");
sleep(30);
//释放锁
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
sleep(1)
while(1){
int ret=pthread_mutex_trylock(&mutex);
if(ret>0)
{
printf("ret=%d,srrmsg:%s\n",ret,strerror(ret));
}
sleep(1);
}
return 0;
}
错误信息在errno-base.h中:
04 死锁
死锁:
- 锁了又锁,自己加了一次锁成功,又加了一次。
- 交叉锁
互斥量只是建议锁。
05 读写锁
读写锁的特点:读共享,写独占,写优先级高。
读写锁仍然是一把锁,有不同的状态:
- 为加锁
- 读锁
- 写锁
读写锁场景练习:
-
线程A加写锁成功,线程B请求读锁
线程B阻塞 -
线程A持有读锁,线程B请求写锁
线程B阻塞 -
线程A拥有读锁,线程B请求读锁
B加锁成功 -
线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
BC阻塞
A释放后,B加锁
B释放后,C加速 -
线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
BC阻塞
A释放,C加锁
C释放,B加锁
读写锁的使用场景:适合读的线程多。
初始化
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
释放锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
rwlock.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
int beginnum=1000;
void *thr_write(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
printf("---%s---self---%lu---beginnum---%d\n",_FUNCTION_,pthread_self(),++beginnum);
usleep(2000);//模拟占用时间
pthread_rwlock_unlock(&rwlock);
usleep(3000);
}
return NULL;
}
void *thr_read(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("---%s---self---%lu---beginnum---%d\n",_FUNCTION_,pthread_self(),++beginnum);
usleep(2000);//模拟占用时间
pthread_rwlock_unlock(&rwlock);
usleep(2000);
}
return NULL;
}
int main()
{
int n=8,i=0;
pthread_t tid[8];//5-read,3-write
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,thr_read,NULL);
}
for(;i<8;i++)
{
pthread_create(&tid[i],NULL,thr_write,NULL);
}
for(i=0;i<8;i++)
{
pthread_join(tid[i],NULL);
}
return 0;
}
读共享,写独占。
06 条件变量介绍和生产者和消费者模型
条件变量不是锁,要和互斥量组合使用
超时等待
int pthread_cond_timedwait(pthread_cond_t *restict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
struct timespec{
time_t ty_sec; /second/秒
long ty_nsec;/nanoseconds/纳秒
}
ty_sec 绝对时间,填写的时候time(NULL)+600==>设置超时600s
条件变量阻塞等待
int pthread_cond_wait(pthread_cond_t *restict cond,pthread_mutex_t *restrict mutex);
- 先释放锁mutex;
- 阻塞在cond条件变量上
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *restict cond);
初始化一个条件变量
int pthread_cond_init(pthread_cond_t restict cond,const pthread_condattr restric attr);
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
唤醒至少一个阻塞在条件变量cond上的线程
int pthread_cond_signal(pthread_cond_t * cond);
唤醒阻塞在条件变量cond上的全部线程
int pthread_cond_broadcast(pthread_cond_t * cond);
07 条件变量介绍和生产者和消费者实现
cond_product.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PHTREAD_COND_INITIALIZER;
int beginnum=1000;
typedef struct _ProdInfo
{
int num;
struct _ProdInfo *next;
}ProdInfo;
ProdInfo *Head=NULL;
void *thr_producter(void *arg)
{
//负责在链表添加数据
while(1)
{
ProdInfo * prod=malloc(sizeof(ProdInfo));
prod->num=beginnum++;
printf("----%s------self=%lu----%d\n",_FUNCTION_,pthread_self(),prod->num);
pthread_mutex_lock(&mutex);
//add to list
prod->next=Head;
Head=prod;
pthread_mutex_unlock(&mutex);
//发起通知
pthread_cond_signal(&cond);
sleep(rand()%2);
}
return NULL;
}
void *thr_customer(void *arg)
{
ProdInfo *prod=NULL;
while(1)
{
//取链表的数据
pthread_mutex_lock(&mutex);
while(Head==NULL)
{
pthread_cond_wait(&cond,&mutex);//在此之前必须先加锁
}
prod=Head;
Head=Head->next;
printf("----%s------self=%lu----%d\n",_FUNCTION_,pthread_self(),prod->num);
pthread_mutex_unlock(&mutex);
sleep(rand()%4);
free(prod);
}
return NULL;
}
int main()
{
pthread_t tid[3];
pthread_create(&tid[0],NULL,thr_producter,NULL);
pthread_create(&tid[1],NULL,thr_customer,NULL);
pthread_create(&tid[2],NULL,thr_customer,NULL);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
08 信号量的概念和函数
信号量是进化版的互斥量,允许多个线程访问共享资源。
int sem_init(sem_t *sem,int pshared,unsigned int value);
-
sem定义的信号量,传出
-
pshared
0代表线程信号量
非0代表进程信号量 -
value 定义信号量的个数
sem_destroy摧毁信号量
int sem_destroy(sem_t *sem);
申请信号量,申请成功value–;
int sem_wait(sem_t *sem)
- 当信号量为0时,阻塞
释放信号量value++
int sem_post(sem_t *sem)
09 信号量实现生产者和消费者分析
10 信号量实现生产者和消费者实现
sem_product.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
sem_t black,xfull;
#define _SEM_CNT_ 5
int queue[_SEM_CNT_];//模拟饼框
int beginnum=100;
void *thr_producter(void *arg)
{
int i=0;
while(1)
{
sem_wait(&blank);//申请资源 blank--
printf("----%s-----self--%lu----num----%d\n",_FUNCTION_,pthread_self(),beginnum);
queue[(i++)%_SEM_CNT_]=beginnum++;
sem_post(&xfull);//xfull++;
sleep(rand()%3);
}
return NULL;
}
void *thr_customer(void *arg)
{
int i=0;
int num=0;
while(1)
{
sem_wait(&xfull);
num=queue[(i++)%_SEM_CNT_];
printf("----%s-----self--%lu----num----%d\n",_FUNCTION_,pthread_self(),num);
sem_post(&blank);
sleep(rand()%3);
}
return NULL;
}
int main()
{
sem_init(&blank,0,_SEM_CNT);
sem_init(&xfull,0,0);//消费者一开始的初始化默认没有产品
pthread_t tid[2];
pthread_create(&tid[0],NULL,thr_producter,NULL);
pthread_create(&tid[1],NULL,thr_customer,NULL);
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
sem_destroy(&blank);
sem_destroy(&xfull);
return 0;
}
11 文件锁单开进程
int fcntl(int fd,int cmd,long arg);
int fcnt1(int fd, int cmd, struct flock *lock)
函数传入值cmd
F_DUPFD:复制一个现存的描述符
F_GETFD:获得fd的close-on-exec(执行时关闭)文件描述符标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态
F_SETFD:设置close-on-exec 标志,该标志由参数arg 的FD_CLOEXEC位决定
F_GETFL:得到open设置的标志
F_SETFL :改变open设置的标志
F_GETLK:根据lock参数值,决定是否可以上文件锁
F_SETLK:设置lock参数值的文件锁
flock.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/type.h>
#include<sus/stat.h>
#include<fcnt1.h>
#define _FILE_NAME_ "/home/itheima/temp.lock"
int main()
{
int fd=open(_FILE_NAME_,O_RDWR|O_CREAT,0666);
if(fd<0)
{
perror("open err");
return -1;
}
struct flock lk;
lk.l_type=F_WRLCK;
lk.l_whence=SEEK_SETl
lk.l_start=0;
lk.l_len=0;
if(fcntl(fd,F_SETLK,&lk)<0)
{
perror("get lock err");
exit(1);
}
//核心逻辑
while(1)
{
printf("I am alive!\n");
sleep(1);
}
return 0;
}