大家好,欢迎来到IT知识分享网。
为什么要线程同步?
- 线程间有很多共享资源,都对一个共享数据读写操作,线程操作共享资源的先后顺序不确定,可能会造成数据的冲突
看一个例子
两个线程屏行对全局变量count++ (采用一个val值作为中间变量,模拟寄存器工作方式,后面会详解)
#include <stdlib.h> #include <pthread.h> #include <unistd.h> #define NLOOP 5000 //循环次数 int count = 0;//全局资源 void* func(void* p) { int i,val; for (i = 0; i < NLOOP; i++) { val = count; printf("count = %d\n",val+1); count = val + 1; usleep(100);//减缓线程执行速度,增加资源冲突概率 } return NULL; } int main() { pthread_t tidA, tidB; pthread_create(&tidA, NULL, &func, NULL);//线程A 对count++ pthread_create(&tidB, NULL, &func, NULL);//线程B 对count++ sleep(1); pthread_join(tidA, NULL); pthread_join(tidB, NULL); return 0; }
第一次执行结果
第二次执行结果
第三次执行结果
一段代码执行三次出现不同的结果,这是为什么?就是因为两个线程同时对共享资源进行操作,导致CPU处理共享资源出现错误
寄存器处理数据+1操作一般分为三步
- 从内存读变量值到寄存器
- 寄存器的值加1
- 将寄存器的值写回内存
假设变量值为5那么CPU执行线程A,将变量读到寄存器中,寄存器的值+1(正在加,此时值还为5),同时线程B将变量从内存中读走(值也为5),线程A将寄存器值写回变量(此时值为6),之后线程B处理完写回变量(此时值还是为6)
那么怎么解决这种情况发生?
一、互斥量
- 互斥量是pthread_mutex_t类型的变量。
- 互斥量有两种状态:lock(上锁)、unlock(解锁)
- 当对一个互斥量加锁后,其他任何试图访问互斥量的线程都会被堵塞,直到当前线程释放互斥锁上的锁。如果释放互斥量上的锁后,有多个堵塞线程,这些线程只能按一定的顺序得到互斥量的访问权限,完成对共享资源的访问后,要对互斥量进行解锁,否则其他线程将一直处于阻塞状态。
简单理解为:超市中的储存柜,当有人使用储存柜后,其他人使用不了,只能等待使用者使用完,再使用
互斥量操作原理
- 创建锁可以通过直接定义锁,或者调用初始化锁函数,二选一
#include <pthread.h> //pthread_mutex_t是锁类型,用来定义互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //互斥锁的初始化 //restrict,C语言中的一种类型限定符,用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。 第二个参数一般为NULL int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //上锁 int pthread_mutex_lock(pthread_mutex_t *mutex); //判断是否上锁 //返回值:0表示已上锁,非0表示未上锁。 int pthread_mutex_trylock(pthread_mutex_t *mutex); //解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); //销毁互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);
对之前的例子加上互斥量
#include <stdlib.h> #include <pthread.h> #include <unistd.h> #define NLOOP 5000 //循环次数 int count = 0;//全局资源 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;//定义锁 void* func(void* p) { int i, val; for (i = 0; i < NLOOP; i++) { pthread_mutex_lock(&counter_mutex);//上锁 val = count; printf("count = %d\n", val + 1); count = val + 1; pthread_mutex_unlock(&counter_mutex);//解锁 usleep(100);//减缓线程执行速度,增加资源冲突概率 } return NULL; } int main() { pthread_t tidA, tidB; pthread_create(&tidA, NULL, &func, NULL);//线程A 对count++ pthread_create(&tidB, NULL, &func, NULL);//线程B 对count++ sleep(1); pthread_join(tidA, NULL); pthread_join(tidB, NULL); return 0; }
不管怎么执行,结果都是10000次
死锁的情况
- 同一个线程已拥有A锁的情况下,再次请求获取A锁,导致线程阻塞
解决方法:使用完资源后立刻解锁 - 线程一拥有A锁,再次请求获取B锁,同时线程二拥有B锁,请求获取A锁,导致线程阻塞
解决方法:当拥有锁的情况下,请求获取另外一把锁失败时,释放已拥有的锁
c/c++Linux服务器开发高阶知识点视频学习资料的朋友后台私信【架构】获取
二、条件变量
- 条件变量就是一个变量,用来自动阻塞一个线程,直到某特殊情况发生为止。
- 条件变量是用来等待事件
- 通常条件下变量和互斥锁同时使用。
条件变量操作原语
#include <pthread.h> //全局定义条件变量 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //初始化条件变量 //cond参数为条件变量指针,通过该函数实现条件变量赋初值;cond_attr参数通常为NULL int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); //销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond); //自动释放mutex锁,等待条件满足 //这个函数的过程我们必须了解,首先对互斥锁进行解锁;然后自身堵塞等待;当等待条件达成,注意这时候函数并未返回,而是重新获得锁并返回。 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); //自动释放mutex锁,等待条件满足,如果在abstime时间内还没有满足,则返回错误 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime); //让等待条件满足的线程中某一个被唤醒 int pthread_cond_signal(pthread_cond_t *cond); //让等待条件满足的线程中全部被唤醒 (广播) int pthread_cond_broadcast(pthread_cond_t *cond);
生产者消费模型
流程首先消费者需要访问共享资源首先要去拿到锁访问条件变量,条件变量说现在还没有资源,所以消费者释放锁,阻塞等待,直到生产者生产出资源后,将资源先放到公共区后,再告诉条件变量,现在有资源了,然后条件变量再去唤醒阻塞线程,这些阻塞的线程被唤醒后需要去争抢锁,先拿到锁的线程优先访问共享资源
实例
#include <stdlib.h> #include <pthread.h> #include <stdio.h> typedef struct msg { struct msg *next; int num; }MSG_T; MSG_T *head;//消息头结点 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//互斥锁 //pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //条件变量 本文通过init定义条件变量 pthread_cond_t mycond; //生产者 void *producer(void *p) { MSG_T* mp; for (;;) { mp = malloc( sizeof(MSG_T) ); mp->num = rand() % 1000 + 1;//生成随机数1到1000 printf("Produce %d\n", mp->num); //将资源放入公共区 pthread_mutex_lock(&lock); mp->next = head; head = mp; pthread_mutex_unlock(&lock); //通知条件变量唤醒线程 pthread_cond_signal(&mycond); sleep(rand() % 5); } } //消费者 void *consumer(void *p) { MSG_T* mp; for (;;) { pthread_mutex_lock(&lock);//上锁 while (head == NULL)//当没有数据时,wait阻塞等待 pthread_cond_wait(&mycond, &lock);//当没有拿到锁的时候 释放锁并等待 mp = head; head = mp->next; pthread_mutex_unlock(&lock);//解锁 printf("Consume %d\n", mp->num); free(mp); sleep(rand() % 5); } } int main(int argc, char *argv[]) { pthread_t pid, cid; if (pthread_cond_init(&mycond, NULL) != 0) { printf("cond error'\n"); exit(1); } srand(time(NULL));//加入随机因子 pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); return 0; }
运行结果
三、信号量
- 信号量分为有名信号量和无名信号量,这里主要介绍无名信号量,用于线程同步,有名信号量一般是用于进程之间管理。
- 信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问,也被称为PV原子操作
- P操作,即信号量sem减一的过程,如果sem小于等于0,P操作被堵塞,直到sem变量大于0为止。P操作及加锁过程。
- V操作,即信号量sem加一的过程。V操作及解锁过程。
简单理解:假设现在有五把钥匙,当有人拿走一把后(P操作),就剩4把钥匙,直到钥匙被拿完,后面的人需要等待,直到使用者将钥匙归还(V操作)
信号量操作原理
#include <semaphore.h> //初始化 //sem: 要进行初始化的信号量对象 //pshared:控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量 //value:赋给信号量对象的一个整数类型的初始值调用成功时 返回 0; int sem_init(sem_t *sem,int pshared,unsigned value); //p操作 -1 int sem_wait(sem_t *sem); //v操作 +1 int sem_post(sem_t *sem); //销毁信号量 int sem_destory(sem_t *sem);
信号量的生产消费者模型
#include <stdlib.h> #include <pthread.h> #include <stdio.h> #include <semaphore.h> #define NUM 5 int queue[NUM];//定义一个环形队列 sem_t blank_number, product_number;//定义两个信号量 //生产者 void *producer(void *arg) { int p = 0; while (1) { sem_wait(&blank_number);//信号量blank_number--,可以比喻成盘子(最多5个盘子)盘子数量-1, queue[p] = rand() % 1000 + 1;//产生随机数 printf("Produce %d\n", queue[p]); sem_post(&product_number);//信号量product_number++ ,比喻成菜,现在菜的数量+1 消费者可以使用 p = (p + 1) % NUM;//队列下标偏移 sleep(rand() % 5); } } //消费者 void *consumer(void *arg) { int c = 0; while (1) { sem_wait(&product_number);//信号量product_number--,现在菜的数量-1 printf("Consume %d\n", queue[c]); queue[c] = 0;//清空当前下标队列 sem_post(&blank_number);//信号量 blank_number++,资源使用完了 盘子数量+1 c = (c + 1) % NUM;//队列下标偏移 sleep(rand() % 5); } } int main(int argc, char *argv[]) { pthread_t pid, cid; sem_init(&blank_number, 0, NUM);//值为5 sem_init(&product_number, 0, 0);//初始值为0 pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); sem_destroy(&blank_number); sem_destroy(&product_number); return 0; }
运行结果
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/46915.html