先来看一个程序:
#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阻塞
-
线程A持有读锁,线程B请求写锁
i. 线程B阻塞
-
线程A拥有读锁,线程B请求读锁
i. B加锁成功
-
线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
i. B阻塞,C阻塞—写的优先级高
ii. A解锁,B加写锁成功,
iii. B解锁,C加读锁成功 -
线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
i. BC阻塞
ii. A解锁,C加写锁成功,B继续阻塞
iii. C解锁,B加读锁成功。
5、读写锁的适用场景?
- 互斥锁—读写串行
- 读写锁
i. 读:并行
ii. 写:串行 - 程序读操作>写操作
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做了++操作
如果一个线程加锁之后,没有解锁就退出了,另一个线程会不会加锁加锁成功?
不会,另一个线程会阻塞在加锁位置。