拂晓-千云暗组- 努力,执着,意志,精神
  • 饥饿和公平(转)

    2017-04-24

    如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性” – 即所有线程均能公平地获得运行机会。

    下面是本文讨论的主题:

    1. Java中导致饥饿的原因:

    • 高优先级线程吞噬所有的低优先级线程的CPU时间。
    • 线程被永久堵塞在一个等待进入同步块的状态。
    • 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。

    2. 在Java中实现公平性方案,需要:

    • 使用锁,而不是同步块。
    • 公平锁。
    • 注意性能方面。

    Java中导致饥饿的原因

    在Java中,下面三个常见的原因会导致线程饥饿:

    1. 高优先级线程吞噬所有的低优先级线程的CPU时间。
    2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
    3. 线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

    高优先级线程吞噬所有的低优先级线程的CPU时间

    你能为每个线程设置独自的线程优先级,优先级越高的线程获得的CPU时间越多,线程优先级值设置在1到10之间,而这些优先级值所表示行为的准确解释则依赖于你的应用运行平台。对大多数应用来说,你最好是不要改变其优先级值。

    线程被永久堵塞在一个等待进入同步块的状态

    Java的同步代码区也是一个导致饥饿的因素。Java的同步代码区对哪个线程允许进入的次序没有任何保障。这就意味着理论上存在一个试图进入该同步区的线程处于被永久堵塞的风险,因为其他线程总是能持续地先于它获得访问,这即是“饥饿”问题,而一个线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。

    线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象

    如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

    在Java中实现公平性

    虽Java不可能实现100%的公平性,我们依然可以通过同步结构在线程间实现公平性的提高。

    首先来学习一段简单的同步态代码:

    1 public class Synchronizer{
    2
    3     public synchronized void doSynchronized(){
    4
    5     //do a lot of work which takes a long time
    6
    7     }
    8 }

    如果有一个以上的线程调用doSynchronized()方法,在第一个获得访问的线程未完成前,其他线程将一直处于阻塞状态,而且在这种多线程被阻塞的场景下,接下来将是哪个线程获得访问是没有保障的。

    使用锁方式替代同步块

    为了提高等待线程的公平性,我们使用锁方式来替代同步块。

    1 public class Synchronizer{
    2     Lock lock = new Lock();
    3     public void doSynchronized() throws InterruptedException{
    4         this.lock.lock();
    5         //critical section, do a lot of work which takes a long time
    6         this.lock.unlock();
    7     }
    8 }

    注意到doSynchronized()不再声明为synchronized,而是用lock.lock()和lock.unlock()来替代。

    下面是用Lock类做的一个实现:

    01 public class Lock{
    02
    03     private boolean isLocked      = false;
    04
    05     private Thread lockingThread = null;
    06
    07     public synchronized void lock() throws InterruptedException{
    08
    09     while(isLocked){
    10
    11         wait();
    12
    13     }
    14
    15     isLocked = true;
    16
    17     lockingThread = Thread.currentThread();
    18
    19 }
    20
    21 public synchronized void unlock(){
    22
    23     if(this.lockingThread != Thread.currentThread()){
    24
    25          throw new IllegalMonitorStateException(
    26
    27               "Calling thread has not locked this lock");
    28
    29          }
    30
    31     isLocked = false;
    32
    33     lockingThread = null;
    34
    35     notify();
    36
    37     }
    38 }

    注意到上面对Lock的实现,如果存在多线程并发访问lock(),这些线程将阻塞在对lock()方法的访问上。另外,如果锁已经锁上(校对注:这里指的是isLocked等于true时),这些线程将阻塞在while(isLocked)循环的wait()调用里面。要记住的是,当线程正在等待进入lock() 时,可以调用wait()释放其锁实例对应的同步锁,使得其他多个线程可以进入lock()方法,并调用wait()方法。

    这回看下doSynchronized(),你会注意到在lock()和unlock()之间的注释:在这两个调用之间的代码将运行很长一段时间。进一步设想,这段代码将长时间运行,和进入lock()并调用wait()来比较的话。这意味着大部分时间用在等待进入锁和进入临界区的过程是用在wait()的等待中,而不是被阻塞在试图进入lock()方法中。

    在早些时候提到过,同步块不会对等待进入的多个线程谁能获得访问做任何保障,同样当调用notify()时,wait()也不会做保障一定能唤醒线程(至于为什么,请看线程通信)。因此这个版本的Lock类和doSynchronized()那个版本就保障公平性而言,没有任何区别。

    但我们能改变这种情况。当前的Lock类版本调用自己的wait()方法,如果每个线程在不同的对象上调用wait(),那么只有一个线程会在该对象上调用wait(),Lock类可以决定哪个对象能对其调用notify(),因此能做到有效的选择唤醒哪个线程。

    公平锁

    下面来讲述将上面Lock类转变为公平锁FairLock。你会注意到新的实现和之前的Lock类中的同步和wait()/notify()稍有不同。

    准确地说如何从之前的Lock类做到公平锁的设计是一个渐进设计的过程,每一步都是在解决上一步的问题而前进的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。这些本身的讨论虽已超出本文的范围,但其中每一步的内容都将会专题进行讨论。重要的是,每一个调用lock()的线程都会进入一个队列,当解锁后,只有队列里的第一个线程被允许锁住Farlock实例,所有其它的线程都将处于等待状态,直到他们处于队列头部。

    01 public class FairLock {
    02     private boolean           isLocked       = false;
    03     private Thread            lockingThread  = null;
    04     private List<QueueObject> waitingThreads =
    05             new ArrayList<QueueObject>();
    06
    07   public void lock() throws InterruptedException{
    08     QueueObject queueObject           = new QueueObject();
    09     boolean     isLockedForThisThread = true;
    10     synchronized(this){
    11         waitingThreads.add(queueObject);
    12     }
    13
    14     while(isLockedForThisThread){
    15       synchronized(this){
    16         isLockedForThisThread =
    17             isLocked || waitingThreads.get(0) != queueObject;
    18         if(!isLockedForThisThread){
    19           isLocked = true;
    20            waitingThreads.remove(queueObject);
    21            lockingThread = Thread.currentThread();
    22            return;
    23          }
    24       }
    25       try{
    26         queueObject.doWait();
    27       }catch(InterruptedException e){
    28         synchronized(this) { waitingThreads.remove(queueObject); }
    29         throw e;
    30       }
    31     }
    32   }
    33
    34   public synchronized void unlock(){
    35     if(this.lockingThread != Thread.currentThread()){
    36       throw new IllegalMonitorStateException(
    37         "Calling thread has not locked this lock");
    38     }
    39     isLocked      = false;
    40     lockingThread = null;
    41     if(waitingThreads.size() > 0){
    42       waitingThreads.get(0).doNotify();
    43     }
    44   }
    45 }

    01 public class QueueObject {
    02
    03     private boolean isNotified = false;
    04
    05     public synchronized void doWait() throws InterruptedException {
    06
    07     while(!isNotified){
    08         this.wait();
    09     }
    10
    11     this.isNotified = false;
    12
    13 }
    14
    15 public synchronized void doNotify() {
    16     this.isNotified = true;
    17     this.notify();
    18 }
    19
    20 public boolean equals(Object o) {
    21     return this == o;
    22 }
    23
    24 }

    首先注意到lock()方法不在声明为synchronized,取而代之的是对必需同步的代码,在synchronized中进行嵌套。

    FairLock新创建了一个QueueObject的实例,并对每个调用lock()的线程进行入队列。调用unlock()的线程将从队列头部获取QueueObject,并对其调用doNotify(),以唤醒在该对象上等待的线程。通过这种方式,在同一时间仅有一个等待线程获得唤醒,而不是所有的等待线程。这也是实现FairLock公平性的核心所在。

    请注意,在同一个同步块中,锁状态依然被检查和设置,以避免出现滑漏条件。

    还需注意到,QueueObject实际是一个semaphore。doWait()和doNotify()方法在QueueObject中保存着信号。这样做以避免一个线程在调用queueObject.doWait()之前被另一个调用unlock()并随之调用queueObject.doNotify()的线程重入,从而导致信号丢失。queueObject.doWait()调用放置在synchronized(this)块之外,以避免被monitor嵌套锁死,所以另外的线程可以解锁,只要当没有线程在lock方法的synchronized(this)块中执行即可。

    最后,注意到queueObject.doWait()在try – catch块中是怎样调用的。在InterruptedException抛出的情况下,线程得以离开lock(),并需让它从队列中移除。

    性能考虑

    如果比较Lock和FairLock类,你会注意到在FairLock类中lock()和unlock()还有更多需要深入的地方。这些额外的代码会导致FairLock的同步机制实现比Lock要稍微慢些。究竟存在多少影响,还依赖于应用在FairLock临界区执行的时长。执行时长越大,FairLock带来的负担影响就越小,当然这也和代码执行的频繁度相关。

     

    Author:admin | Categories:工作间 | Tags:
  • mac 安装php扩展时执行 phpize 报错:

    2016-05-09

    /usr/bin/phpize执行之后报如下错误:

    grep: /usr/include/php/main/php.h: No such file or directory
    grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
    grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
    Configuring for:
    PHP Api Version:
    Zend Module Api No:
    Zend Extension Api No:

    网上找了下一般是mac升级后导致include找不到了,建立个软链可解决问题:

    sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer

    /SDKs/MacOSX10.11.sdk/usr/include/ /usr/include

    注意标红处不同的mac系统下可能是不一样的,根据实际情况进行修改。

    Author:admin | Categories:php | Tags:
  • (转)谈谈Redis的SETNX

    2016-05-06

    文章中讲到的问题我在项目中真实遇到过,就是setnx成功了,但expire没成功,导致锁一直存在。所以觉得很有借鉴的意义,就转了过来。
    原文:http://huoding.com/2015/09/14/463
    在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!
    阅读更多 »

    Author:admin | Categories:工作间 | Tags:
  • (转)TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇

    2016-04-12

    1. 查看系统网络配置和当前TCP状态
    在定位并处理应用程序出现的网络问题时,了解系统默认网络配置是非常必要的。以x86_64平台Linux kernelversion 2.6.9的机器为例,ipv4网络协议的默认配置可以在/proc/sys/net/ipv4/下查看,其中与TCP协议栈相关的配置项均以tcp_xxx命名,关于这些配置项的含义,请参考这里的文档,此外,还可以查看linux源码树中提供的官方文档(src/linux/Documentation/ip-sysctl.txt)。下面列出我机器上几个需重点关注的配置项及其默认值:
    阅读更多 »

    Author:admin | Categories:Linux | Tags:
  • 人类不可抗拒理论(转)

    2015-10-04

    原作者:曹洲(Anderson)
    人类不可抗拒理论
      当我走在路上、坐在咖啡厅里、乘坐公共交通工具的时候,我观察每一个人。无论他们是在看报、听音乐、玩手机或是搅拌咖啡。我惊讶的发现,包括我自己在内,我们居然再做同样的事!——消磨多余的时间。
      然而什么时间是多余的?多余的时间是如何产生的?什么样的群体、运用什么样的方法?使用什么样的产品、会有什么的效果来帮助人们消磨多余的时间?成为我即将研究的课题。 阅读更多 »

    Author:admin | Categories:读书笔记 | Tags:
  • 解决phpredis ‘RedisException’ with message ‘read error on connection’

    2015-08-26

    在跑定时任务时常出现一个Exception如下:
    ‘RedisException’ with message ‘read error on connection’

    是php.ini文件中的一个配置项导致:

    default_socket_timeout = 60
    由于redis扩展也是基于php 的socket方式实现,因此该参数值同样会起作用。

    找到了问题就比较好解决了:

    1、直接修改php.ini,将其设置为我们想要的值(这个不推荐)

    2、在我们的脚本中通过以下方式设置,这样就比较灵活,不对其他脚本产生影响

    ini_set(‘default_socket_timeout’, -1); //不超时

    Author:admin | Categories:工作间 | Tags:
  • mongodb 分片技术(转)

    2015-08-21

    在mongodb里面存在另一种集群,就是分片技术,跟sql server的表分区类似,我们知道当数据量达到T级别的时候,我们的磁盘,内存

    就吃不消了,针对这样的场景我们该如何应对。
    阅读更多 »

    Author:admin | Categories:工作间 | Tags:
  • 提交错误报告段错误

    2015-04-08

    前几天项目要用swoole,折腾半天在用里面的异步IO时老是报Segmentation fault。提取了下段错误信息向开发组报告,这里记录下查询调用栈信息的方法:

    打开core dump

    ulimit -c unlimited
    [wyc@AY140512203815195f76Z async]$ php testio.php
    Segmentation fault (core dumped)

    阅读更多 »

    Author:admin | Categories:Linux | Tags:
  • 二进制

    2015-03-21
    二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”
    当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0。
    表示法:
    二进制数据也是采用位置计数法,其位权是以2为底的幂。例如二进制数据110.11,逢2进1,其权的大小顺序为2²、2¹、2º、 、 。对于有n位整数,m位小数的二进制数据用加权系数展开式表示
    二进制和十六进制,八进制一样,都以二的幂来进位的。

    阅读更多 »

    Author:admin | Categories:读书笔记 | Tags:
  • PHP命令行参数详解及应用

    2014-10-26
    下面是全部的php命令行参数,其中[]表示可有可无的,<>表是一定要的。 用法 php [-q] [-h] [-s] [-v] [-i] [-f ] | { [args...]} -q 安静模式。不输出HTTP头。
    -s 将php程序文件转化为彩色格式的HTML(比如保留字用绿色,函数和变量为蓝色,注释为黄色而字串则是红色等等。

    阅读更多 »

    Author:admin | Categories:php | Tags: