线程同步:
协同步调,对公共区域的数据排队访问。防止数据混乱。产生与时间有关的错误。
一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程为保证数据一致性,不能调用该功能。
列子:银行存款5000.柜台存折:取3000; 提款机,卡 :取 3000 。剩余:2000
锁的使用:
建议锁 ! 对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但是,锁本身不具有强制性。
使用mutex(互斥量 互斥锁)一般步骤:
1.pthread_mutex_t lock; pthread_mutex_t 本质是一个结构体 为了方便想象成 int
2.pthread_mutex_init; 初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
3.pthread_mutex_lock; 加锁
4.访问共享数据(stdout)
5.pthread_mutex_unlock();解锁
6.pthread_mutex_destroy;销毁锁
都是成功返回0 失败返回 errno
初始化互斥量:
pthread_mutex_t mutex;
1. pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
2.pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
//模拟多线程操作共享资源
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#include<pthread.h>
pthread_mutex_t mutex;//定义一把互斥锁可以想象为一个 int 数据
void* tfn(void* arg){
srand(time(NULL));
while(1){
// pthread_mutex_lock(&mutex);//加锁 可以想象成 锁 -- 1 --》 0
printf("hello ");
sleep(rand()%3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
printf("world\n");
// pthread_mutex_unlock(&mutex);//解锁 可以想象成 锁++ (0++ --》1);
sleep(rand()%3);
}
return NULL;
}
int main(){
pthread_t tid;
srand(time(NULL));
int ret = pthread_mutex_init(&mutex,NULL);//初始化互斥锁 可以认为 锁的值 为 1
if(ret!=0){
fprintf(stderr,"pthread_mutex_init error %s\n",strerror(ret));
exit(1);
}
pthread_create(&tid,NULL,tfn,NULL);
while(1){
//pthread_mutex_lock(&mutex);
printf("HELLO ");
sleep(rand()%3);
printf("WORLD\n");
//pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
pthread_join(tid,NULL);
pthread_mutex_destroy(&mutex);//销毁互斥锁
return 0;
}
未加锁输出:
HELLO hello WORLD
world
HELLO WORLD
HELLO hello WORLD
world
HELLO WORLD
hello world
HELLO hello WORLD
world
HELLO WORLD
hello world
hello HELLO WORLD
加锁后:
HELLO WORLD
hello world
hello world
hello world
HELLO WORLD
hello world
注意事项:
尽量保证锁的粒度,越小越好。(访问共享数据前加锁。访问结束立即 解锁。)
互斥锁,初值为 1. (pthread_mutex_init()函数调用成功)
加锁: --操作
解锁: ++操作
try锁: 尝试加锁。 成功--。 失败 ,返回错误号(EBUSY)不阻塞
restrict关键字:
用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成
死锁 :
使用锁不恰当导致的现象
1.对一个锁反复lock (解决:加完锁 立即解锁)
2.两个线程 ,各自持有一把锁,请求另一把 (解决:trylock 当不能获取所有的锁时,放弃已经占有的锁)
读写锁:
锁只有一把。以读方式给数据加锁--读锁 。以写方式给数据加锁 --写锁
读共享 ,写独占
写锁优先级高
相较于互斥量而言,当读线程多时,提高访问效率
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock,NULL)
pthread_rwlock_wrlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_rdlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);
成功返回 0 失败 errno
读写锁特性:
写模式加锁时,解锁前,所有对该锁加锁的线程都会被阻塞
读模式加锁时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞
读模式加锁时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。读写锁会阻塞随后的读模式锁请求。优先满足写模式。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int counter;
pthread_rwlock_t rwlock;
//3个线程不定时写同一全局资源 5个线程不定时读同一全局资源
void* th_write(void* arg){
int t;
int i= (int)arg;
while(1){
pthread_rwlock_wrlock(&rwlock);//写模式加锁 写独占
t=counter;
usleep(1000);
printf("=========write %d: %lu : ++ counter =%d\n",i,pthread_self(),++counter);
pthread_rwlock_unlock(&rwlock);
usleep(10000);
}
return NULL;
}
void* th_read(void* arg){
int i= (int)arg;
while(1){
pthread_rwlock_rdlock(&rwlock);//读线程间 ,读锁共享
printf("----------------read %d: %lu: %d\n",i,pthread_self(),counter);
pthread_rwlock_unlock(&rwlock);
usleep(2000);
}
return NULL;
}
int main(){
int i;
pthread_t tid[8];
pthread_rwlock_init(&rwlock,NULL);
for(i=0;i<3;++i)
pthread_create(&tid[i],NULL,th_write,(void*)i);
for(i=0;i<5;++i)
pthread_create(&tid[i+3],NULL,th_read,(void*)i);
for(i=0;i<8;++i)
pthread_join(tid[i],NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
输出:
--------------read 3: 140386488055552: 267
----------------read 0: 140386513233664: 267
----------------read 1: 140386504840960: 267
----------------read 4: 140386479662848: 267
=========write 0: 140386538411776 : ++ counter =268
=========write 1: 140386530019072 : ++ counter =269
----------------read 4: 140386479662848: 269
----------------read 0: 140386513233664: 269
----------------read 1: 140386504840960: 269
----------------read 3: 140386488055552: 269
----------------read 2: 140386496448256: 269
----------------read 4: 140386479662848: 269
----------------read 0: 140386513233664: 269
----------------read 1: 140386504840960: 269
----------------read 3: 140386488055552: 269
----------------read 2: 140386496448256: 269
条件变量:
本身不是锁!但是通常结合锁来使用 。 mutex
pthread_cond_t cond;
初始化条件变量:
1.pthread_cond_init(&cond,NULL); 动态初始化
2.pthread_cond_t cond = PTHREAD_COND_INITIALIZER ; 静态初始化
pthread_cond_signal(); 唤醒阻塞在条件变量上的(至少)一个线程
pthread_cond_broadcast();唤醒阻塞在条件变量上的所有线程
//条件变量 互斥量 实现生产者消费者模型
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
void err_thread(int ret,const char* str){
if(ret!=0){
fprintf(stderr,"%s :%s\n",str,strerror(ret));
pthread_exit(NULL);
}
}
struct msg{
int num;
struct msg* next;
};
struct msg* head;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定义初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;//定义初始化一个条件变量
void* producer(void* arg){
(void)arg;
while(1){
struct msg* mp =(struct msg*)malloc(sizeof(struct msg));
mp->num=rand()%1000+1; //模拟生产一个数据
printf("--produce %d\n",mp->num);
pthread_mutex_lock(&mutex); //加锁
mp->next=head;
head=mp;
pthread_mutex_unlock(&mutex);//解锁
pthread_cond_signal(&has_data); //唤醒阻塞在条件变量 has_data 上的线程
sleep(rand()%3);
}
return NULL;
}
void* consumer(void* arg){
(void)arg;
while(1){
struct msg* mp;
pthread_mutex_lock(&mutex);//加锁
while(head ==NULL){
pthread_cond_wait(&has_data,&mutex);
}
mp=head;
head=mp->next;
pthread_mutex_unlock(&mutex); //解锁互斥量
printf("-----consumer id:%lu:%d\n",pthread_self(),mp->num);
free(mp);
sleep(rand()%3);
}
return NULL;
}
int main(){
pthread_t pid,cid;
int ret;
srand(time(NULL));
ret = pthread_create(&pid,NULL,producer,NULL);//生产者
if(ret!=0)
err_thread(ret,"pthread_create producer error");
ret = pthread_create(&cid,NULL,consumer,NULL);// 消费者
if(ret!=0)
err_thread(ret,"pthread_create consumer error");
ret = pthread_create(&cid,NULL,consumer,NULL);// 消费者
if(ret!=0)
err_thread(ret,"pthread_create consumer error");
ret = pthread_create(&cid,NULL,consumer,NULL);// 消费者
if(ret!=0)
err_thread(ret,"pthread_create consumer error");
pthread_join(pid,NULL);
pthread_join(cid,NULL);
return 0;
}
输出:
--produce 837
-----consumer id:140241653298944:837
--produce 76
-----consumer id:140241644906240:76
--produce 358
-----consumer id:140241644906240:358
--produce 764
--produce 538
-----consumer id:140241644906240:538
-----consumer id:140241653298944:764
注意:当有多个消费者时 条件变量判断 用 while 循环
信号量:
相当于 初始值为 N 的 互斥量。N值表示可以同时访问共享数据区的线程数。
函数:
sem_init();
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem:信号量
pshared: 0:用于线程间同步
1:用于进程间同步
value: N值 。(指定同时访问你的线程数)
sem_destroy(sem_t *sem);
sem_wait(sem_t *sem); 一次调用 ,做一次 -- 操作,当信号量的值为 0 时, 再次-- 就会阻塞。(对比 phread_mutex_lock)
sem_post(sem_t *sem); 一次调用 ,做一次 ++操作,当信号量的值为 N 时, 再次++ 就会阻塞。(对比 phread_mutex_unlock)
//信号量实现生产者消费者模型
#include <semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#define NUM 5
int queue[NUM]; //全局数据实现环形队列
sem_t blank_number,product_number; //空格子信号量 ,产品信号量
void err_thread(int ret,const char* str){
if(ret!=0){
fprintf(stderr,"%s :%s\n",str,strerror(ret));
pthread_exit(NULL);
}
}
void* producer(void* arg){
int i= 0;
while(1){
sem_wait(&blank_number); //生产者将空格子数--,为0则阻塞等待
queue[i] = rand()%1000+1;
printf("-----Produce ---%d\n",queue[i]);
sem_post(&product_number);//将产品数++
i = (i+1)%NUM; //借助下标实现环形
sleep(rand()%1);
}
}
void* consumer(void* arg){
int i =0;
while(1){
sem_wait(&product_number); //消费者将产品数-- ,为0 则阻塞等待
printf("--Consume ---%d\n",queue[i]);
queue[i]= 0; //消费一个产品
sem_post(&blank_number); //消费掉以后,将空格子数++
i = (i+1)% NUM;
sleep(rand()%3);
}
}
int main(){
pthread_t pid,cid;
int ret;
sem_init(&blank_number,0,NUM); //初始化空格子信号量为5 ,线程将共享 ---- 0
sem_init(&product_number,0,0); //产品数为0
srand(time(NULL));
ret = pthread_create(&pid,NULL,producer,NULL);//生产者
if(ret!=0)
err_thread(ret,"pthread_create producer error");
ret = pthread_create(&cid,NULL,consumer,NULL);// 消费者
if(ret!=0)
err_thread(ret,"pthread_create producer error");
pthread_join(pid,NULL);
pthread_join(cid,NULL);
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;
}
输出:
-----Produce ---152
-----Produce ---373
-----Produce ---22
-----Produce ---681
-----Produce ---506
--Consume ---152
-----Produce ---779
--Consume ---373
--Consume ---22
--Consume ---681