zoukankan      html  css  js  c++  java
  • 多线程是什么?锁是什么?信息量是什么?以及各自的用处

    线程的概念:

    • 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
    • 线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
    • 线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU。

    多线程的概念

    • 多线程是指从软件或者硬件上实现多个线程并发执行的技术.
    • 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
    • 最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。

    如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制:

    • 连续的操作,需要花费忍无可忍的过长时间才可能完成
    • 并行计算
    • 为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    • 所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    为什么需要多线程(解释何时考虑使用线程)

    • 从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
    • 程序包含复杂的计算任务时,主要是利用多线程获取更多的CPU时间(资源)。
    • 处理速度较慢的外围设备.比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
    • 程序设计自身的需要.WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
    • 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行

    线程的优先级

    • 优先级的取值为1-10(数值越高优先级越高)。
    • Public final int getPriority();  得到线程优先级的数值。
    • Public final void setPriority(int newPriority);修改线程的优先级。
    • 注:优先级高不代表该线程就一定先运行,只能代表该线程先运行的可能型比较大。

    控制线程周期常用的方法

    • Wait()释放CPU的执行权,释放锁。
    • Notify()回到wait前的状态。
    • Yied()让线程临时暂停。(让线程将资源释放出来)
    • Join()让该线程强行加入执行。
    • SetDaemon(true)设置该线程为后台线程(当前台线程结束时,后台线程一定会一起结束)。
    • 注:结束线程原理就是让run方法结束,所以只要控制run的流程即可。

    为什么要线程同步

    • 线程间共享代码和数据可以节省系统开销,提高效率。但也同时会导致“数据访问冲突”。如何实现线程间有机交互,并确保共享资源在某时只能被一个线程访问,就是线程同步。
    •   多个线程间共享的数据称为临界资源。

    注意: 代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

    多线程的同步与互斥:

    方式一:锁

    • 在主线程中初始化锁为解锁状态
      • pthread_mutex_t mutex;
      • pthread_mutex_init(&mutex, NULL);
    • 在编译时初始化锁为解锁状态
      • 锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    • 访问对象时的加锁操作与解锁操作
      • 加锁 pthread_mutex_lock(&mutex)
      • 释放锁 pthread_mutex_unlock(&mutex)

    互斥锁

    •   每个对象都对应一个互斥锁标记,可以保证在某一时刻只能有一个线程访问该对象。
    •   互斥锁的关键字 synchronized 可以写在某个方法上(代表锁调用该方法的对象);  可以括在要锁的语句外。
    • 好处:解决了线程安全的问题
    • 弊端:降低了运行效率(判断锁,且不能共享信息);容易出现死锁。

    死锁:

    • 两个线程A,B用到同一个对象s(s为共享资源),且线程A在执行中要用到B运行后所创造条件。在这种前提下A先开始运行,进入同步块后,对象s被锁定,接着线程A因等待B运行结束而进入阻塞状态,于是B开始运行,但因无法访问对象s,线程B也进入阻塞状态,等待s被线程A解锁。最终的结果:两个线程互相等待,都无法运行。

    方式二:信号量

    锁有一个很明显的缺点,那就是它只有两种状态:锁定与不锁定。

    信号量本质上是一个非负数的整数计数器,它也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。

    它们是在/usr/include/semaphore.h中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数

    相关函数

    在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>

    • 初始化信号量: int sem_init(sem_t *sem, int pshared, unsigned int value);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量结构的一个指针
      • pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
      • value:信号量的初始值
    • 信号量减1操作,当sem=0的时候该函数会堵塞 int sem_wait(sem_t *sem);
      • 成功返回0,失败返回-1
      • 参数
      • sem:指向信号量的一个指针
    • 信号量加1操作 int sem_post(sem_t *sem);
      • 参数与返回同上
    • 销毁信号量 int sem_destroy(sem_t *sem);
      • 参数与返回同上

    信号量和锁的区别

    信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”

    也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进 行操作。在有些情况下两者可以互换。

    两者之间的区别:

    作用域

    信号量: 进程间或线程间(Linux仅线程间的无名信号量pthread semaphore)

    互斥锁: 线程间

    上锁时 

    信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一

    互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源

    以下是信号灯(量)的一些概念:

    信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资 源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;

    没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持 灯亮状态。当然,这样的操作原语也意味着更多的开销。

    信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

     原子操作

    多进程线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。

     
  • 相关阅读:
    Jenkins自动化多项目编译和Tomcat部署懒人终极大招
    python 装饰器总结
    selenium3之-测试环境搭建
    centos7.4 安装ftp服务器并配置匿名用户权限
    selenium3之-运行原理
    flutter 打包apk
    Fluwx:微信SDK在Flutter上的实现
    flutter 购物车功能
    flutter sharesdk实现跨平台分享
    Web API接口设计经验总结
  • 原文地址:https://www.cnblogs.com/ssssdy/p/7133061.html
Copyright © 2011-2022 走看看