为什么要线程同步?

  • 线程间有很多共享资源,都对一个共享数据读写操作,线程操作共享资源的先后顺序不确定,可能会造成数据的冲突

看一个例子

两个线程屏行对全局变量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 = %dn",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 = %dn", 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服务器开发高阶知识点视频学习资料的朋友加qun720209036获取

二、条件变量

  • 条件变量就是一个变量,用来自动阻塞一个线程,直到某特殊情况发生为止。
  • 条件变量是用来等待事件
  • 通常条件下变量和互斥锁同时使用。

条件变量操作原语

#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到1000printf("Produce %dn", 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 %dn", 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 5int 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 %dn", 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--,现在菜的数量-1printf("Consume %dn", queue[c]);queue[c] = 0;//清空当前下标队列sem_post(&blank_number);//信号量 blank_number++,资源使用完了 盘子数量+1c = (c + 1) % NUM;//队列下标偏移sleep(rand() % 5);}
}int main(int argc, char *argv[])
{pthread_t pid, cid;sem_init(&blank_number, 0, NUM);//值为5sem_init(&product_number, 0, 0);//初始值为0pthread_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;
}

运行结果

原文链接:Linux线程同步(互斥量、信号量、条件变量、生产消费者模型)_赵小厨的博客-CSDN博客_互斥量条件变量信号量

c++ linux 线程等待与唤醒_Linux线程同步(互斥量、信号量、条件变量、生产消费者模型)...相关推荐

  1. 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量

    https://blog.csdn.net/kxcfzyk/article/details/31719687 线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程 ...

  2. Linux c线程间的同步----互斥锁、条件变量、信号量

    线程 一个进程中的所有线程共享为进程分配的地址空间.所以进程地址空间中的代码段和数据段都是共享的. 如果定义一个函数在各个线程中都可以调用,定义一个全部变量,在各个线程中都可以访问到. 各线程共享资源 ...

  3. c++ linux 线程等待与唤醒_Linux驱动程序基石-POLL机制(附.视频)

    今天<升级版全系列嵌入式视频_入门篇>新增一节视频:19.2_POLL机制 时长24分钟,免费观看 何为POLL机制? 给驱动程序加一个闹钟,让APP不必死等数据: 既可以快速掌握 POL ...

  4. 信号灯文件锁linux线程,linux——线程同步(互斥量、条件变量、信号灯、文件锁)...

    一.说明 linux的线程同步涉及: 1.互斥量 2.条件变量 3.信号灯 4.文件读写锁 信号灯很多时候被称为信号量,但个人仍觉得叫做信号灯比较好,因为可以与"SYSTEM V IPC的信 ...

  5. Linux下互斥量与条件变量详细解析

    1. 首先pthread_cond_wait 的定义是这样的 The pthread_cond_wait() and pthread_cond_timedwait() functions are us ...

  6. c++ linux 线程等待与唤醒_C++ Linux线程同步机制:POSIX信号量,互斥锁,条件变量...

    线程同步机制:POSIX 信号量,互斥量,条件变量 POSIX 信号量 常用的POSIX 信号量函数为如下5个: sem_init sem_destroy sem_wait sem_trywait s ...

  7. Linux下多线程编程互斥锁和条件变量的简单使用

    Linux下的多线程遵循POSIX线程接口,称为pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,链接时需要使用库libpthread.a.线程是进程的一个实体,是CPU ...

  8. Windows事件等待学习笔记(二)—— 线程等待与唤醒

    Windows事件等待学习笔记(二)-- 线程等待与唤醒 要点回顾 等待与唤醒机制 可等待对象 可等待对象的差异 线程与等待对象 一个线程等待一个对象 实验 第一步:编译并运行以下代码 第二步:在Wi ...

  9. java队列等待唤醒_Java深入学习29:线程等待和唤醒的两个方案

    Java深入学习29:线程等待和唤醒的两个方案 模拟场景 一个门店,有一个店员,有消费者来消费商品(每次消费1件商品),有仓库人员来添加(生产)商品(每次生产1件商品),并假设库存上限是2. 基础代码 ...

最新文章

  1. 企业想独立完成网站建设也不是不可以
  2. Mac安装python3的opencv包
  3. C语言Cruskal算法查找最小生成树(附完整源码)
  4. shell之case和循环语句(case语句的格式与举例)(for循环,while循环until循环语句的详解和continue,break解释, 九九乘法口诀表 ,等腰三角形)
  5. Python ——告白小程序,添加微信号(快来设置你的freestyle吧)
  6. react table里跳转页面_react路由配置基础篇:react-router4.0及以上
  7. 零基础学Python的几个经典例子
  8. 常用JavaScript 收集
  9. eigrp配置实验_EIGRP负载均衡的实现
  10. 分享 MSDN 下载工具(Word/PDF)
  11. html怎么做卫星图地址,如何基于卫星地图制作矢量化电子地图
  12. html里怎么画斜线表头,excel里斜线表头怎么做
  13. u盘数据恢复软件哪个好?怎么恢复u盘数据?
  14. 【AVD】NDK MediaCodec 编码中的坑 configure: err(-2147479551) error -38 Fatal signal 4 (SIGILL) ILL_ILLOPC
  15. 断舍离---新生活方式指引
  16. 微信小程序和uni-app面试高频知识点
  17. ji计算机内存不足怎么回事,Win7提示内存不足的原因及应对措施
  18. AGV路径规划方法——A*算法
  19. 病毒木马查杀实战第009篇:QQ盗号木马之手动查杀
  20. 分布式对偶平均法(DDA)

热门文章

  1. bat 执行 java jar包
  2. Hystrix降级逻辑中如何获取触发的异常
  3. 程序员每周该做的事情!
  4. [DRBD] UpToDate/DUnknown 故障恢复
  5. 获取url的hash值
  6. asp.net2.0安全性(3)--验证与授权
  7. 文本比较算法Ⅲ——计算文本的相似度
  8. 3月第3周新闻回顾:3Com案三日动荡 珊瑚虫作者入狱3年
  9. php程序员应该懂的几个基本概念
  10. C++学习笔记35:函数模板