学习目标
互斥量的使用
lock和unlock的使用例子
通过互斥量,两个线程交替打印
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
//常量初始化锁——mutex(这样就不用init函数了),将其定义为全局变量
pthread_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;
}
trylock
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
pthread_mutex_t mutex;
void *thr(void *arg)
{
while(1){
pthread_mutex_lock(&mutex);
printf("hello world\n");
sleep(30);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
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;
}
返回值的错误码是16,我们来看一下16代表什么意思。
错误码定义的地方
死锁
产生条件
- 锁了又锁,自己加了锁,自己又加了一把锁。一般都是有分支或者写代码的时候忘了会出现这个问题
- 交叉锁(如下图所示)——解决方案:1、每个线程申请锁的顺序要一致;2、如果申请到一把锁,另一个申请不到,则释放已有资源
互斥量只是建议锁
读写锁
读共享,写互斥,写的优先级高。适合读的线程多的场景
读写锁仍然是一把锁,有不同的状态:
- 未加锁
- 读锁
- 写锁
读写锁练习场景
初始化
一种初始化的方式
另一种初始化的方式
销毁读写锁
读写锁的例子
#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(4000);
}
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(){
//创建5个读锁和3个写锁
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;
}
条件变量
引入原因:mutex会产生如下问题
多个线程抢到锁之后,发现并没有资源,因此释放锁,然后继续多线程抢锁,然后释放锁,这户造成资源的浪费。
三个吃货抢锁,抢到的人打开饼筐发现没有饼,于是释放锁,然后三个吃货继续抢锁。
条件变量可以引起阻塞,并非锁,要和互斥变量组合使用
超时等待
传入参数中的timespec结构体:
条件变量阻塞等待
使用条件变量解决问题的例子
两个线程,一个线程想把所有的内容都写成0,另一个线程想把所有的内容都写成1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
pthread_mutex_t mutex ;
char buf[20];
void *thr1(void *arg){
int i = 0 ;
//加锁
pthread_mutex_lock(&mutex);
for(;i< 20; i ++){
usleep(rand()%3);
buf[i] = '0';
}
//解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
void *thr2(void *arg) {
int i = 0 ;
//加锁
pthread_mutex_lock(&mutex);
for (; i < 20; i++) {
usleep(rand() % 3);
buf[i] = '1';
}
//解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main(){
//两个线程,一个线程想把所有的内容都写成0,另一个线程想把所有的内容都写成1
memset(buf,0x00,sizeof(buf));
//初始化条件变量
pthread_mutex_init(&mutex, NULL);
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);
printf("buf is %s\n",buf);
//释放条件变量
pthread_mutex_destory(&mutex);
return 0;
}
条件变量解决生产着消费者模型
一个生产者一个消费者时
用链表存储数据
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
int beginnum = 1000;
//初始化mutex
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
//初始化条件变量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
typedef struct _ProdInfo{
int num;
struct _ProdInfo *next;
}ProdInfo;
ProdInfo *HEAD = NULL;
void *thr_producer(void *arg){
//负责向链表添加数据
while(1){
ProdInfo *prod=malloc(sizeof(ProdInfo));
prod->num=beginnum++;
//上锁
pthread_mutex_lock(&mutex);
//add to list
prod->next=HEAD;
HEAD=prod;
printf("--------%s--------seld=%lu------------%d\n",__FUNCTION__,pthread_self(), prod->num);
pthread_mutex_unlock(&mutex);
//发起通知
pthread_cond_signal(&cond);
sleep(rand()%4);
}
return NULL;
}
void *thr_customer(void *arg){
ProdInfo *prod=NULL;
while(1){
//取链表的数据
pthread_mutex_lock(&mutex);
//判断有没有数据
if(HEAD==NULL){
//发送消息等待
pthread_cond_wait(&cond, &mutex);
}
//此时链表非空
prod=HEAD;
HEAD=HEAD->next;
printf("--------%s--------seld=%lu------------%d\n",__FUNCTION__,pthread_self(), prod->num);
//解锁
pthread_mutex_unlock(&mutex);
sleep(rand()%4);
free(prod);
}
return NULL;
}
int main(){
pthread_t tid[2];
pthread_create(&tid[0], NULL, thr_producer, NULL);
pthread_create(&tid[1], NULL, thr_customer, NULL);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
一个生产者多个消费者时
把thr_customer中的
if(HEAD==NULL)
改成
while(HEAD==NULL)
即可。
信号量
加强版的互斥锁。适于多个资源多个线程访问的情况。
初始化
- sem 定义的信号量,传出
- pshared 非0则代表进程信号量, 0代表线程信号量
- value 定义信号量的并发数(钥匙的个数)
摧毁信号量
申请信号量,申请成功,则value--
释放信号量 value++
信号量实现生产者消费者模型
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
//blank只有多少可以放生产者生产东西的地方,xfull是消费者可以消费的东西的数量
sem_t blank,xfull;
#define _SEM_CNT_ 5
// 模拟饼筐
int queue[_SEM_CNT_];
int beginnum = 100;
void *thr_producter(void *arg) {
int i = 0;
while(1){
//申请资源 blank--
//看看能不能生产,还有没有空间
sem_wait(&blank);
//打印函数名、线程名、数量
printf("-----%s-----self--%lu----num----%d\n",__FUNCTION__,pthread_self(),beginnum);
//生产数据
queue[(i++)%_SEM_CNT_] = beginnum++;
//xfull ++
sem_post(&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(){
//线程,所有第二个参数是0, 如果是进程,则第二个参数是非0
sem_init(&blank,0,_SEM_CNT_);
//消费者一开始的初始化默认没有产品
sem_init(&xfull,0,0);
pthread_t tid[2];
//线程没有设置属性,所有第二个参数为NULL
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;
}
文件锁
适合环境——当前系统中该进程只能起一个。
实现原理——当一个进程打开了这个文件,另一个进程发现文件被打开了,就无法再打开这个文件了。
文件锁——读共享,写独占
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.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_SET ;
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;
}
哲学家用餐问题