为什么会产生线程安全问题

  • 线程之间通信极为方便灵活,但是多个线程作为多个执行流对同一个临界资源进行访问,就有可能出现数据二义性,所以这就产生了线程安全

  • 临界资源:多个线程执行流共享的资源就叫做临界资源

  • 如何实现线程安全
    1.同步: 多个线程之间对临界资源访问的时序合理性
    —通过对方是否满足对临街组员的操作条件来判断线程是否等待或唤醒这种方式实现对临界资源访问的时序合理性
    2.互斥: 线程之间对临界资源访问的安全性
    —同一时间只有一个线程访问

  • 互斥实现

  • 互斥锁(死锁) :通过0/1计数器来实现两种标记(访问或不能访问)

  • 实现加锁/解锁操作:必须保证操作的原子性
    1.原子性: 不会被任何调度机制打断的操作, 该操作只有两态, 要么完成 , 要么未完成

  • pthread_mutex_init (pthread_mutex_t *mutex , NULL)\

  • int phread_mutex_destroy(pthread_mutex_t *mutex);

  • pathread_mutex_lock(pathread_mutex_t *mutex)阻塞加锁–无法加锁则挂机等待

  • pathread_mutex_ trylock(pathread_mutex_t *mutex)非阻塞加锁-----无法加锁则立即报错返回

  • pathread_mutex_unlock(pathread_mutex_t *mutex)—解锁

  • 以票贩子抢票为案例实现互斥

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int ticket = 100;
pthread_mutex_t  mutex;void *ticket_scalper(void *arg)
{char* id = (char*)arg;while(1){//互斥锁保护的是临界资源,加锁放在临界操作之前//int pthread_mutex_lock(pthread_mutex_t *mutex);//阻塞加锁--无法加则挂起等待//int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁--无法加锁则立即报错返回pthread_mutex_lock(&mutex);if(ticket > 0){printf("%sticket_scalper:---get a ticket:%d\n", id ,ticket);ticket--;}else{//需要在线程任意有可能推出的地方进行解锁pthread_mutex_unlock(&mutex);pthread_exit(NULL);}//临界资源操作完毕后一定记得解锁pthread_mutex_unlock(&mutex);}return NULL;
}int main()
{int i = 0, ret;pthread_t tid[4];//互斥锁初始化//int pthread_mutex_init(pthread_mutex_t *mutex,//  const pthread_mutexattr_t *restrict attr);//  mutex:互斥锁变量//  attr:互斥锁属性(通常置NULL)pthread_mutex_init(&mutex , NULL);for(i = 0 ; i < 4 ; i++){ret = pthread_create(&tid[i] , NULL , ticket_scalper , (void*)i);if(ret != 0){printf("create scalper failed !!\n");return -1;}}for(i = 0 ; i < 4 ; i++){pthread_join(tid[i] , NULL);}//销毁互斥锁,释放资源//int pthread_mutex_destroy(pthread_mutex_t *mutex)pthread_mutex_destroy(&mutex);return 0;
}
  • 同步实现

  • 若当前不能对临界资源进行访问, 则让线程等待, 等待到临界资源满足被线程访问的条件年 , 则唤醒线程的等待

  • 条件变量: 通过向外提供接口 , 实现等待与唤醒功能来实现线程间的同步
    1.同步让用户可以对当前临界资源访问状态来判断是否等待或唤醒
    2.竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

  • 因未提供条件判断的功能, 到第一个线程什么时候应该等待 , 什么时候唤醒,所以这些需用户自己做判断
    1.用户对条件判断需要使用循环判断–防止当前不符合条件被浣熊之后因为不循, 因此直接操作临界资源
    2.不同的角色线程应该等待在不同的条件变量队列上–防止角色误唤醒, 导致程序阻塞

  • pathread_cond_t cond

  • pathread_cond_init(pathread_cond_t *, pthread_condattr_t *)

  • pathread_cond_wait(pathread_cond_t * , pathread_mutex_t *)–解锁+等待+被唤醒后加锁

  • pathread_cond_singnal(pathread_cond_t *)–唤醒至少一个线程

  • pathread_cond_timedwait(pathread_cond_t *)----限时等待

  • pathread_cond_braoadcast–广播唤醒所有等待的线程

  • pathread_cond_destroy(pathread_cond_t *)–销毁

  • 以在面馆吃面中厨师做面和顾客吃面为案例实现同步

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int is_have_noodles = 0;
pthread_mutex_t mutex;
pthread_cond_t hall_foodie;
pthread_cond_t kitchen_chef;void *thr_chef()
{while(1){pthread_mutex_lock(&mutex);while(is_have_noodles == 1){pthread_cond_wait(&kitchen_chef , &mutex);}printf("made a bowl of noodles\n");is_have_noodles = 1;pthread_mutex_unlock(&mutex);pthread_cond_signal(&hall_foodie);}return NULL;
}void *thr_foodie()
{while(1){pthread_mutex_lock(&mutex);while(is_have_noodles == 0){pthread_cond_wait(&hall_foodie , &mutex);}printf("delicous\n");pthread_mutex_unlock(&mutex);pthread_cond_signal(&kitchen_chef);}
}int main()
{int ret , i;pthread_t ctid[4] , etid[4];pthread_mutex_init(&mutex , NULL);pthread_cond_init(&hall_foodie , NULL);pthread_cond_init(&kitchen_chef , NULL);for(int i = 0 ; i < 4 ; i++){ret = pthread_create(&ctid[i] , NULL , thr_chef ,(void*)i);if(ret != 0){printf("create chef error\n");return -1;}}for(i = 0 ; i < 4 ; i++){ret = pthread_create(&etid[i] , NULL , thr_foodie ,(void*)i);if(ret != 0){printf("create foodie error\n");return -1;}}for(int i = 0 ; i < 4 ; i ++){pthread_join(ctid[i] , NULL);pthread_join(etid[i] , NULL);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&hall_foodie);pthread_cond_destroy(&kitchen_chef);return 0;
}
  • 信号量
    实现进程/线程间同步与互斥

    • posix标准信号量----库函数
      1.线程间—全局计数器
      2.进程间—共享内存
      3.接口
1.sem_t
2.sem_init(sem_t *sem, int flag , int val) flag: 0--线程间 !0--进程间   initval  设置计数器初值
3.sem_wait(sem_t *sem)//计数-1,进行判断是否有资源,<0则阻塞>0则调用返回
4.sem_trywait(sem_t *sem) ---非阻塞--没有资源则直接报错返回
5.sem_timedwait(sem_t *sem)--等待一段时间,期间若一直没有资源则报错返回
6.sem_post(sem_t *sem)--计数+1并且唤醒等待
7.sem_destroy(sem_t *sem)
  • system V 信号量
    内核中的计数器----系统调用

  • 同步实现
    等待+唤醒+等待队列 条件变量+信号量

    • 条件变量: 等待+唤醒+等待队列(条件需要用户进行外部判断)
      1.需要搭配互斥锁一起使用
    • 信号量: 通过自身内部计数实现的条件的判断
      1.不需用搭配互斥锁(自身计数需保持原子操作)
    • 信号量通过一个计数器对资源的计数, 并且通过对这个计数来判断当前资源是否能够对临界资源进行访问(对临界资源访问之前先发起调用访问信号量进行判断是否能够访问)
      1.若计数器>0 则可以进行访问 ,调用直接返回, 并且计数-1
      2.若计数<=0 则表示没有资源 ,无法访问 ,调用阻塞(挂起线程)
      3.若其他线程产生一个资源, 则发起调用, 资源计数+1,唤醒队列上的线程
  • 互斥实现
    互斥锁+信号量
    1.计数只有0/1 ; 用户标记两种状态(资源只有一个,同一时间就只有一个线程能够访问)
    2.对临界资源访问之前先进行判断计数, 若>0, 则计数-1, 并且调用返回 , 对临界资源进行访问,访问完毕 ,计数+1, 唤醒所有技术 ,重新开始抢夺

  • 用vector实现线程安全的环形队列: RingQueue

    • 通过read/write指向初始位置

      • read指针和write指针位置相同表示队列中无数据
      • 没取出一个数据read指针后移/ 添加一个数据write指针后移
        1.write位置指向末尾位置时需要回到初始位置
        2.write每次移动都需考虑下一位置是否是read指针的位置(表示位置满了)
      • read每次移动都需考虑下一位是否和write指针是否位置相同(位置相同则表中无数据)
  • 死锁
    多个线程对锁资源进行争抢 ,因为推进的顺序不当而导致的相互等待, 是程序流程无法继续向下

    • 达成死锁的四个条件
      1.互斥条件
      2.不可剥夺条件
      3.请求与保持条件
      4.环路等待条件
    • 死锁的预防: 破坏必要条件一个或多个
    • 死锁的避免:银行家算法\
  • 读写锁

    • 写互斥, 读共享
    • 实现:
      • 两个计数器
        1.读者计数
        2.写者计数
      • 加写锁 : 对两个计数器进行判断若任意一个计数器>0 ;都无法加写锁, 需要等待
      • 加读锁: 对写着计数进行判断, 若>0 则无法加读锁 , 需要等待
    • 读写锁是通过自旋锁实现的: 不满足条件时自旋等待
      1.等待中不停地循环对条件进行判断—自旋等待
  • 读写锁
    一直循环 , 在它占用CPU时 , 是独占CPU其他条件无法打断

    • 使用场景 : 对数据的操作时间确定很短的情况
    • 对雨刮器等待被唤醒的时间相较于数据处理时间可以忽略不计的情况下(倾向挂起等待)

Linux-------线程安全相关推荐

  1. [转载]Linux 线程实现机制分析

    自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads ...

  2. Linux 线程的创建与同步

    Linux 线程的创建与同步 1.线程的定义 2.线程的创建和使用 3.理解线程的并发运行 3.线程同步 3.线程的实现 1.线程的定义 线程:进程内部的一条执行路径.是资源调度和执行的基本单位. 进 ...

  3. linux 线程操作问题undefined reference to ‘pthread_create‘的解决办法(cmake)

    linux 线程操作问题undefined reference to 'pthread_create'的解决办法(cmake) 参考文章: (1)linux 线程操作问题undefined refer ...

  4. linux线程的实现【转】

    转自:http://www.cnblogs.com/zhaoyl/p/3620204.html 首先从OS设计原理上阐明三种线程:内核线程.轻量级进程.用户线程 内核线程 内核线程就是内核的分身,一个 ...

  5. linux 线程 进程经典文章

    进程是程 序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是 死的(静态的),进程是活的(动态的).进程可以分为系统进程和用户进程.凡是用于完成操作系统的各种功能的进程就是 ...

  6. linux 线程--内核线程、用户线程实现方法

    Linux上进程分3种,内核线程(或者叫核心进程).用户进程.用户线程 内核线程拥有 进程描述符.PID.进程正文段.核心堆栈 当和用户进程拥有相同的static_prio 时,内核线程有机会得到更多 ...

  7. Linux 线程与进程,以及通信

    http://blog.chinaunix.net/uid-25324849-id-3110075.html 部分转自:http://blog.chinaunix.net/uid-20620288-i ...

  8. Linux线程-互斥锁pthread_mutex_t

    Linux线程-互斥锁pthread_mutex_t 在线程实际运行过程中,我们经常需要多个线程保持同步.这时可以用互斥锁来完成任务:互斥锁的使用过程中,主要有pthread_mutex_init, ...

  9. 【Linux开发】彻底释放Linux线程的资源

    Linux系统中程序的线程资源是有限的,表现为对于一个程序其能同时运行的线程数是有限的.而默认的条件下,一个线程结束后,其对应的资源不会被释放,于是,如果在一个程序中,反复建立线程,而线程又默认的退出 ...

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

    为什么要线程同步? 线程间有很多共享资源,都对一个共享数据读写操作,线程操作共享资源的先后顺序不确定,可能会造成数据的冲突 看一个例子 两个线程屏行对全局变量count++ (采用一个val值作为中间 ...

最新文章

  1. 二分法求解一元多次方程
  2. scrapy two
  3. 卓同学的 Swift 面试题
  4. selenium.common.exceptions.WebDriverException: Message: ‘chromedriver‘ executable needs to bein PATH
  5. Good Time 冲刺 六
  6. navicat修改表的主键自增长报错
  7. JS 实现3D立体效果的首页轮播图(瞬间让你的网站高大上,逼格满满)
  8. 学习人工智能的头四个月
  9. neu坐标系和xyz坐标系转换_ArcGIS投影坐标系下坐标转换成地理坐标系经纬度
  10. WCF 第十二章 对等网 使用自定义绑定实现消息定向
  11. 从《觉醒年代》看如何用Python来绘制可视化仪表盘
  12. easyui tab页面关闭根据回调函数刷新父tab页
  13. 应用安全 - Web安全 - 上传漏洞 - 攻防
  14. 社会工程学实践前言和开篇
  15. JDownloader 2 for Mac(不限速下载工具)
  16. Vue 技术栈 教你玩坏 v8引擎 吃透 js 内存回收机制
  17. 移动营销必备:App自动绑定的五大场景赋能
  18. MySQL聚集索引和非聚集索引
  19. 风险回避、减轻、转移、接受,汇率风险
  20. dma读nand_使用DMA方式读取spi flash问题求助

热门文章

  1. 用html制作chm,用HTML Help Workshop制作chm.doc
  2. ionic 以及cordova apk打包成功,安装不成功,显示Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]
  3. 基于单片机的车速控制语音报警系统
  4. 北洋 BTP-R150 打印机驱动
  5. oracle删库跑路,DBCA静默删库,悄悄跑路
  6. 摄像头poe供电原理_poe供电原理
  7. 干货教程:数据结构与算法之美
  8. heuristic manner
  9. HBuilderX 下载安装教程
  10. 分页封装实用工具类及其使用方法