热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux多线程(2)

线程的知识点太多,太重要,所以分成三部分进行总结学习线程安全多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

线程的知识点太多,太重要,所以分成三部分进行总结学习

线程安全

多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

多个线程对临界资源进行竞争操作时若不会造成数据二义性时则线程安全;否则,此时就是不安全的

如何实现线程安全

常见的线程安全的情况
  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见的线程不安全的情况
  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

在网上调研过程中看到一个总结:减少对临界资源的依赖,尽量避免访问全局变量,静态变量或其它共享资源,如果必须要使用共享资源,所有使用到的地方必须要进行互斥锁 (Mutex) 保护

所以当对临界资源使用时,尽量在必须的地方使用锁的保护

对临界资源又有两种访问,分别是同步访问和互斥访问

同步:临界资源的合理访问

异步:临界资源同一时间的唯一访问

互斥锁

互斥锁的操作就是1/0的操作

一个0或者1的计数器。1可以表示加锁,加锁就是计数-1;操作完毕之后要解锁,解锁就是计数+1;

0表示不可以加锁,不能加锁则等待

//互斥锁的接口
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//函数应销毁mutex引用的mutex对象
//注意!!!
//销毁已解锁的已初始化互斥体应是安全的。试图销毁锁定的互斥体会导致未定义的行为。

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//mutex:互斥锁变量
//attr:属性,通常为NULL 
//应使用attr指定的属性初始化mutex引用的mutex

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁
//互斥锁变量不一定非要全局变量--只要保证要互斥的线程都能访问到就行

int pthread_mutex_lock(pthread_mutex_t *mutex);
//锁定mutex引用的mutex对象。如果互斥体已被锁定,则调用线程应阻塞,直到互斥体可用。此操作将返回互斥对象引用的互斥对象处于锁定状态,调用线程作为其所有者。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//函数应等同于pthread_mutex_lock(),但如果mutex引用的mutex对象当前被锁定(由任何线程,包括当前线程),则调用应立即返回。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//函数应释放mutex引用的mutex对象。互斥体的释放方式取决于互斥体的type属性。如果在调用pthread_mutex_unlock()时,mutex引用的mutex对象上有线程被阻塞,导致mutex可用,调度策略应确定哪个线程应获取mutex。

互斥锁的操作步骤

  1. 定义互斥锁变量
  2. 初始化互斥锁变量
  3. 加锁
  4. 解锁
  5. 销毁互斥锁

通过一个互斥锁Demo来感受一下锁的使用

//模拟黄牛抢票,100张票,共有四个黄牛在抢票
#include 
#include 
#include 
#include 
#include 

int ticket = 100;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定义初始化锁

void* thr_start(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        if(ticket > 0){
            usleep(1000);
            printf("yellow bull : %d----get ticket : %d\n",(int)arg,ticket);
            ticket--;
        }else{
            pthread_mutex_unlock(&mutex);
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main(int argc, char* argv[]){
    pthread_t tid[4];
    int i = 0,ret;
    
    pthread_mutex_init(&mutex,NULL);
    for(;i <4; ++i){
        ret = pthread_create(&tid[i],NULL,thr_start,(void*)i);
        if(ret != 0){
            printf("yellow bull no exit!");
            return -1;
        }
    }
    for(i = 0;i <4;++i){
        pthread_join(tid[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
	return 0;
}

Linux多线程(2)

这种情况黄牛抢票是比较容易的,一般只有一个黄牛能全抢到票。

但是如果把锁去掉

Linux多线程(2)

这样抢票就很混乱,因为没有了保护。所以锁的使用是在共享资源对它进行保护,换句话说加锁是为了保护资源,所以在这个代码中就将抢票的操作进行加锁保护。这样就只有一个黄牛可以抢到票。

死锁

在进行加锁的过程中很有可能发生死锁的情况下。

在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他的进程所占用不会释放的资源而处于一种永久等待的状态

死锁的四个条件(重点)

1、互斥条件:一个资源一次只能被一个执行流使用

我操作的时候别人不能操作

2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不变

拿着手里的,但是请求其他的,其他的请求不到,手里拿着的也不放开

3、不可剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺

我的锁,别人不能释放

4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

指在发生死锁时,必然存在一个进程资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,Pn正在等待已被P0占用的资源

死锁的产生与处理

当加锁或者解锁顺序不同时会发生死锁的情况;对锁资源的竞争以及进程/线程的加锁的推进顺序b不当

当以上四种条件被破坏时,可以预防死锁的产生

避免死锁的方法可以通过:死锁检测算法,银行家算法(推荐王道视频学习)

同步的实现

条件变量是线程同步的一种手段,条件变量用来自动阻塞一个线程,直到条件满足被触发为止。通常情况下条件变量和互斥锁同时使用

条件变量使我们可以睡眠等待某种条件出现。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

1、一个/多个线程等待“条件变量的条件成立”而挂起;线程1如果操作条件满足,则操作,否则进行等待。

2、另一个线程使“条件成立”信号;线程2促使条件满足,唤醒等待的线程。

如果没有资源则等待(死等),生产资源后唤醒等待。

//条件变量的接口
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//条件变量初始化,一般attr默认为NULL
//使用attr引用的属性初始化cond引用的条件变量。如果attr为空,则使用默认条件变量属性;效果与传递默认条件变量属性对象的地址相同。初始化成功后,条件变量的状态将被初始化。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//静态初始化条件变量

int pthread_cond_destroy(pthread_cond_t *cond);
//销毁由cond指定的给定的条件变量
//销毁当前未阻塞线程的初始化条件变量是安全的。
//试图销毁当前阻止其他线程的条件变量会导致未定义的行为。

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
//abstime:限时等待时长,限时等待时长,超时后则返回

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//解锁后的挂起操作(原子操作),有可能还没来得及挂起就已经有人唤醒--白唤醒--导致了死锁

int pthread_cond_signal(pthread_cond_t *cond);
//唤醒至少一个等待的

int pthread_cond_boardcast(pthread_cond_t *cond);
//广播唤醒,唤醒所有等待的人

条件变量的步骤:

1、定义条件变量

2、初始化条件变量

3、等待\唤醒定义的条件变量

4、销毁条件变量

 //模拟一个skr与cxk使用比赛舞台的Demo                                  
  #include     
  #include     
  #include     
  #include     
  #include     
      
  int have_stage = 1;    
      
  pthread_cond_t skr;    
  pthread_cond_t cxk;    
  pthread_mutex_t mutex;    
      
  void* thr_skr(void* arg){//skr此时要上台dancing    
    while(1){    
      pthread_mutex_lock(&mutex);    
      //若此时舞台有人用,那么skr进行等待    
      while(have_stage == 1){    
        pthread_cond_wait(&skr,&mutex);    
      }    
      //舞台被人使用了,此时0;因为之前1,代表可以使用    
      printf("skr~~ is freestyle!!!\n");    
      sleep(1);    
      //跳完舞后舞台空了出来    
      have_stage += 1;    
      //有舞台了,叫cxk来使用    
      pthread_cond_signal(&cxk);    
      pthread_mutex_unlock(&mutex);
    }
    return NULL;
  }
  
  void* thr_cxk(void* arg){
    while(1){
      pthread_mutex_lock(&mutex);
      //没有舞台,那么在这里等待
      while(have_stage == 0){
        pthread_cond_wait(&cxk,&mutex);
      }
      //有了舞台就是可以唱跳rap篮球了。。
      printf("cxk~~ is singing,dancing,playing rapping and basketball!!\n");
      sleep(1);
      have_stage -= 1;
      //跳完还想跳。。因此叫skr快跳完换他跳。。
      pthread_mutex_unlock(&mutex);
      pthread_cond_signal(&skr);//唤醒skr,
    }
    return NULL;
  }                                                                                               int main(int argc,char * argv[]){
    pthread_t tid1,tid2;
    int ret;
      
    pthread_cond_init(&skr,NULL);
    pthread_cond_init(&cxk,NULL);
    pthread_mutex_init(&mutex,NULL);
    int i = 0;
    for(i = 0;i < 2;i++){
      ret = pthread_create(&tid1,NULL,thr_skr,NULL);
      if(ret != 0){
        printf("skr error");
        return -1;
      }
    }
    for(i = 0;i < 2; i++){
      ret = pthread_create(&tid2,NULL,thr_cxk,NULL);
      if(ret != 0){
        printf("cxk error");
        return -1;
      }
    }
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
  
    pthread_cond_destroy(&skr);
    pthread_cond_destroy(&cxk);
    pthread_mutex_destroy(&mutex);
    return 0;
  } 

Linux多线程(2)

运行结果可以看到cxk和skr交替互斥的表演节目。。。

那么互斥量(mutex)保护的是什么?其实保护的是变量条件(have_stage),当互斥量被成功lock后我们就可以放心的去读取变量条件,这样就不用在担心在这期间变量条件会被其他线程修改。如果变量条件不满足条件,当前线程阻塞,等待其他线程释放条件成立信号,并释放已经lock的mutex。这样一来其他线程就有了修改变量条件的机会。当其他线程释放条件成立信号后,pthread_cond_wait函数返回,并再次lock

pthread_cond_wait的工作流程可以总结为:unlock mutex,start waiting -> lock mutex。

while的作用

在变量条件处为什么不用if做判断而是用while,这是因为pthread_cond_wait的返回不一定意味着其他线程释放了条件成立信号。也可能意外返回。这种被称为假唤醒,在Linux中带阻塞功能的system call都会在进程中收到了一个signal后返回。这就是为什么使用while来检查的原因。因为不能保证wait函数返回的一定就是条件满足,如果条件不满足,那么我们还需要继续等待

signal条件变量的考虑

解锁互斥量mutex和发出唤醒信号是两个单独的操作,所以就存在一个顺序的问题

(1) 按照 unlock(mutex); condition_signal()顺序,当等待线程被唤醒时,因为mutex已经解锁,因此被唤醒的线程(skr)很容易就锁住了mutex然后从conditon_wait()中返回了。

(2) 按照 condition_signal(); unlock(mutex)顺序,当等待线程被唤醒时,它试图锁住mutex,但是如果此时mutex还未解锁,则线程又进入睡眠,mutex成功解锁后,此线程在再次被唤醒并锁住mutex,从而从condition_wait()中返回。


推荐阅读
  • JUC并发编程——线程的基本方法使用
    目录一、线程名称设置和获取二、线程的sleep()三、线程的interrupt四、join()五、yield()六、wait(),notify(),notifyAll( ... [详细]
  • 关于进程的复习:#管道#数据的共享Managerdictlist#进程池#cpu个数1#retmap(func,iterable)#异步自带close和join#所有 ... [详细]
  • 在iOS开发中,多线程技术的应用非常广泛,能够高效地执行多个调度任务。本文将重点介绍GCD(Grand Central Dispatch)在多线程开发中的应用,包括其函数和队列的实现细节。 ... [详细]
  • 面试题总结_2019年全网最热门的123个Java并发面试题总结
    面试题总结_2019年全网最热门的123个Java并发面试题总结 ... [详细]
  • 问题描述现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能;在实际开发过程中 ... [详细]
  • spring boot使用jetty无法启动 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本文介绍了读写锁(RWMutex)的基本概念、实现原理及其在Go语言中的应用。读写锁允许多个读操作并发执行,但在写操作时确保互斥,从而提高并发性能。 ... [详细]
  • 在运行于MS SQL Server 2005的.NET 2.0 Web应用中,我偶尔会遇到令人头疼的SQL死锁问题。过去,我们主要通过调整查询来解决这些问题,但这既耗时又不可靠。我希望能找到一种确定性的查询模式,确保从设计上彻底避免SQL死锁。 ... [详细]
  • 在MFC框架中,存在多个全局函数,用于在不同对象间获取信息或创建新对象。其中,`afxGetApp`函数尤为关键,它能够帮助开发者轻松获取当前应用程序的实例指针。本文将详细解析`afxGetApp`函数的内部机制及其在MFC应用程序中的具体应用场景,探讨其在提升代码可维护性和灵活性方面的优势。此外,还将介绍其他常用全局函数如`AfxWinInit()`和`AfxBeginThread()`的功能和使用方法,为开发者提供全面的参考。 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 欢迎来到Netgen新时代:探索网络生成技术的无限可能
    欢迎进入Netgen的新时代:探索网络生成技术的无限潜力。本文将详细介绍如何编译下载的Netgen源代码,生成Netgen程序,并提供开发所需的库nglib。此外,还将探讨Netgen在现代网络设计与仿真中的应用前景,以及其在提高网络性能和可靠性方面的关键作用。 ... [详细]
  • 在操作系统中,阻塞状态与挂起状态有着显著的区别。阻塞状态通常是指进程因等待某一事件(如I/O操作完成)而暂时停止执行,而挂起状态则是指进程被系统暂时移出内存,以释放资源或降低系统负载。此外,本文还深入分析了`sleep()`函数的实现机制,探讨了其在不同操作系统中的具体实现方式及其对进程调度的影响。通过这些分析,读者可以更好地理解操作系统如何管理进程的不同状态以及`sleep()`函数在其中的作用。 ... [详细]
  • ANR一般有三种类型: ... [详细]
  • ubuntu下基于c++的opencv学习
    一、环境配置1、安装opencv2、makefile编写makefile模板,与c文件在同一个目录下,用make指令生成可执行文件,然后运 ... [详细]
author-avatar
linxin66063
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有