Linux线程同步

先来看一个程序:

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<pthread.h>
#define MAX 10000
//全局变量
int number;
void* funcA_num(void* arg){
        for(int i=0;i<MAX;i++){
                int cur=number;
                cur++;
                number=cur;
                printf("A,id=%lu,number=%d\n",pthread_self(),number);
                usleep(10);
        }
        return NULL;
}
void* funcB_num(void*arg){
        for(int i=0;i<MAX;i++){
                int cur=number;
                cur++;
                number=cur;
                printf("B,id=%lu,number=%d\n",pthread_self(),number);
                usleep(10);
        }
        return NULL;
}
int main(int argc,const char * argv[]){
        pthread_t p1,p2;
        //创建两个子线程
        pthread_create(&p1,NULL,funcA_num,NULL);
        pthread_create(&p2,NULL,funcB_num,NULL);
        //阻塞,资源回收
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);
        return 0;
}

程序可以看出来,每个线程数10000次(number++一千次)最后number的值为20000,但是结果为:

在这里插入图片描述
为什么只到19995呢?

线程1从内存中读取number=0,得到cpu开始++,加到500,这个时候还没有把500写入内存,就失去了cpu,500临时存储
线程2也从内存中读取number=0,得到cpu开始++,加到400,并写入了内存,失去cpu.
线程1又得到了cpu将500写入了内存覆盖了400

数据混乱原因:
1、操作了共享资源
2、cpu调度问题
解决:
1、线程同步
2、什么叫同步:
a.协同不掉,按照先后顺序执行操作

在这里插入图片描述
如果锁是关闭的:
线程阻塞,阻塞在这把锁上
如果锁是打开的:
线程访问共享资源会将这把锁锁上,以防止其它线程访问
通过加锁机制:
并行变成了串行

互斥锁(互斥量)

1、互斥锁的类型

//创建一把锁
pthread_mutext_t mutex;

2、互斥锁的特点、缺点
多个线程访问共享数据的时候是串行执行的
缺点是效率低,加锁解锁需要时间
3、互斥锁的相关函数

//初始化互斥锁:
pthread_mutex_init(
	pthread_mutex_t * restrict mutex,
	const pthread_mutexattr_t * restrict attr
);
//销毁互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex);
//加锁
pthread_mutex_lock(pthread_mutex_t *mutext);
/*如果加锁的时候发现锁已经被锁上了,线程会一直阻塞在这个位置
锁被打开的时候解除阻塞*/
pthread_mutex_trylock(pthread_mutex_t * mutex);
//没有锁上:当前线程会给这个锁加锁
//如果锁上了:不会阻塞,返回
if(pthread_mutex_trylock(&mutex)==0){
	//尝试加锁,并且成功了
	//访问共享资源
}else{
	//错误处理
	//或者等一会,再次尝试加锁
}
//解锁
pthread_mutex_unlock(pthread_mutex_t *mutex);
//如果我们想使用互斥锁同步线程,所有的线程都需要加锁
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<pthread.h>
#define MAX 10000
//全局变量
int number;
//创建一把互斥锁
pthread_mutex_t mutex;
void* funcA_num(void* arg){
        for(int i=0;i<MAX;i++){
                //访问全局变量之前加锁
                //如果mutex被锁上了,代码阻塞在当前位置
                pthread_mutex_lock(&mutex);
                int cur=number;
                cur++;
                number=cur;
                printf("Thread A,id=%lu,number=%d\n",pthread_self(),number);
                //解锁,访问完共享变量后记得解锁
                pthread_mutex_unlock(&mutex);
                usleep(10);
        }
        return NULL;
}
void* funcB_num(void*arg){
        for(int i=0;i<MAX;i++){

                pthread_mutex_lock(&mutex);
                int cur=number;
                cur++;
                number=cur;
                printf("Thread B,id=%lu,number=%d\n",pthread_self(),number);
                pthread_mutex_unlock(&mutex);
                usleep(10);
        }
        return NULL;
}
int main(int argc,const char * argv[]){
        pthread_t p1,p2;
        //初始化互斥锁
        pthread_mutex_init(&mutex,NULL);

        //创建两个子线程
        pthread_create(&p1,NULL,funcA_num,NULL);
        pthread_create(&p2,NULL,funcB_num,NULL);

        //阻塞,资源回收
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);

        //释放锁资源
        pthread_mutex_destroy(&mutex);

        return 0;
}

4、互斥锁的使用步骤:
1)、创建互斥锁
2)、初始化这把锁:pthread_mutex_init(&mutex,NULL);–mutext=1 有一把锁可以使用
3)、寻找共享资源

//操作共享资源加锁
1.	pthread_mutex_lock(&mutex);--mutex=0
2.	 //临界区
3.	pthread_mutext_unlock(&mutex); --mutex=1;

读写锁

1、读写锁是几把锁?
1)、一把锁
2)、pthread_rwlock_t lock;
2、读写锁
1)、读锁-对内存读操作
2)、写锁-对内存写操作
3、读写锁的特性
1)、线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
>>>>读共享-并行处理

2)、线程A加写锁成功,又来了三个线程,做读操作,三个线程阻塞
>>>>写独占

3)、线程A加读锁成功,又来B线程加写阻塞,又来了C线程加读锁阻塞
>>>>读写不能同时
>>>>写的优先级高

4、 读写锁场景练习:
a) 线程A加写锁成功,线程B请求读锁

i.	线程B阻塞
  1. 线程A持有读锁,线程B请求写锁

    i. 线程B阻塞

  2. 线程A拥有读锁,线程B请求读锁

    i. B加锁成功

  3. 线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁

    i. B阻塞,C阻塞—写的优先级高
    ii. A解锁,B加写锁成功,
    iii. B解锁,C加读锁成功

  4. 线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁

    i. BC阻塞
    ii. A解锁,C加写锁成功,B继续阻塞
    iii. C解锁,B加读锁成功。

5、读写锁的适用场景?

  1. 互斥锁—读写串行
  2. 读写锁
    i. 读:并行
    ii. 写:串行
  3. 程序读操作>写操作
    6、主要操作函数
//初始化读写锁
pthread_rwlock_init(
	pthread_rwlock_t *restrict rwlock,
	const pthread_rwlockattr_t * restrict attr);
//销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//加读锁
pthread_rwlock_rdlock(phtread_rwlock_t * rwlock);
//尝试加读锁
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
/*加锁成功:0,失败:错误号*/
//加写锁
pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//尝试加写锁
pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//解锁
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<pthread.h>

int number=0;
pthread_rwlock_t lock;

void * write_func(void * arg){
        //循环写
        while(1){
                //加写锁
                pthread_rwlock_wrlock(&lock);
                number++;
                printf("==write : %ld,%d\n",pthread_self(),number);
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}
void * read_func(void * arg){
        while(1){
                //加读锁
                pthread_rwlock_rdlock(&lock);
                printf("==read : %lu,%d\n",pthread_self(),number);
                pthread_rwlock_unlock(&lock);
                usleep(500);
        }
        return NULL;
}
int main(int argc,const char * argv[]){

        pthread_t p[8];
        //初始化读写锁
        pthread_rwlock_init(&lock,NULL);
        //创建3个写线程
        for(int i=0;i<3;i++){
                pthread_create(&p[i],NULL,write_func,NULL);
        }
        //创建5个读线程
        for(int i=3;i<8;i++){
                pthread_create(&p[i],NULL,read_func,NULL);

        }
        //阻塞回收子线程pcb
        for(int i=0;i<8;i++){
                pthread_join(p[i],NULL);
        }
        //释放读写锁资源
        pthread_rwlock_destroy(&lock);
        return 0;
}

条件变量

1、条件变量
不是锁,但是条件变量能够阻塞线程
使用**条件变量+互斥量**
生产消费者模型:

在这里插入图片描述
2、条件变量的两个动作?
1)条件不满足,阻塞线程
2)条件满足,通知阻塞的线程开始工作
3、条件变量的类型:

pthread_cond_t

4、主要函数

//初始化一个条件变量-condition
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);
//销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
//阻塞等待一个条件变量
pthread_cond_wait(
	pthread_cond_t * restrict cond,
	pthread_mutex_t * restrict mutex
);
/*
内部会做三个事情:
1、阻塞线程
2、将已经上锁的mutex解锁
3、该函数解除阻塞,会对互斥锁加锁
*/
//限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);
//唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.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){
        while(1){
                //创建一个链表的节点
                Node * pnew=(Node*)malloc(sizeof(Node));
                //节点初始化
                pnew->data=rand()%1000;//0-999
                //使用互斥锁保护共享数据
                pthread_mutex_lock(&mutex);
                //指针域
                pnew->next=head;
                head=pnew;
                printf("====producer:%lu,%d\n",pthread_self(),pnew->data);
                pthread_mutex_unlock(&mutex);
                //通知阻塞的消费者线程,解除阻塞
                pthread_cond_signal(&cond);
                sleep(rand()%3);
        }
        return NULL;

}

void * customer(void *arg){

        while(1){
                pthread_mutex_lock(&mutex);
                //判断链表是否为空
                if(head==NULL){
                        //线程阻塞
                        //该函数会会对互斥锁解锁
                        pthread_cond_wait(&cond,&mutex);
                        //解除阻塞之后, 对互斥锁做加锁操作
                }
                //链表不为空-删除一个节点,删除头结点
                Node * pdel=head;
                head=pdel->next;
                printf("====customer:%lu,%d\n",pthread_self(),pdel->data);
                free(pdel);
                pthread_mutex_unlock(&mutex);
        }
        return NULL;
}

int main(int argc,const char *argv[]){

        pthread_t p1,p2;
        //init
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);
        //创建生产者线程

        pthread_create(&p1,NULL,producer,NULL);
        pthread_create(&p2,NULL,customer,NULL);
        
        //阻塞回收子线程
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);
        return 0;
}
信号量(信号灯)

1、头文件

semaphore.h

2、信号类型
sem_t sem;
加强版的互斥锁

在这里插入图片描述
3、主要函数

//初始化信号量
sem_init(sem_t *sem,int pshared,unsigned int value);
//pshared:
	0---进程同步
	1---线程同步
	value-最多有几个线程操作共享数据
//销毁信号量
sem_destroy(sem_t *sem);
//加锁
sem_wait(sem_t *sem);
	sem==0,加锁失败,不阻塞,直接返回
//尝试加锁:
sem_trywait(sem_t *sem);
	sem==0 加锁失败,不阻塞,直接返回
//限时尝试加锁
sem_timedwait(sem_t *sem,xxxx);
//解锁++
sem_post(sem_t *sem);
	对sem做了++操作	

在这里插入图片描述

如果一个线程加锁之后,没有解锁就退出了,另一个线程会不会加锁加锁成功?

不会,另一个线程会阻塞在加锁位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值