> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# 关于锁的总结（二）条件变量
- URL: https://blog.hacking.pub/2018/05/23/condition-variable/
- Published: 2018-05-23T09:21:42.000Z
- Updated: 2018-05-23T10:38:07.000Z
- Author: chainhelen
- Tags: 锁, condition variable, 条件变量, 观察者模式, 线程状态, 阻塞

###### 条件变量

`互斥器`用于`互斥`，`条件变量`用于`等待`  
如果上例子中消费者和生产者同时并发，那么需要消费者需要`轮询`（`polling`）或者`轮转`（`spinning`）  
查询`buf`状态，看是否满足消费条件

```
void comsume_wait(int i) {
    for( ; ; ) {
        pthread_mutex_lock(&shared.mutex);
        if (i < shared.sum) {
            pthread_mutex_unlock(&shared.mutex);
            return;
        }
        pthread_mutex_unlock(&shared.mutex);
    }
}

void *comsume(void *arg) {
    (void) arg;
    for (int i = 0;i < MAXBUFLEN;i++) {
        comsume_wait(i) ;
        if (i != shared.buf[i]) {
            printf("buf[%d] = %d\n", i, shared.buf[i]);
        }
    }
    return (NULL);
}

```

这种方式并不友好，实际是对`CPU时间`的一种浪费

如果使用条件变量的方式，那么上面的代码即可写成如下

```

struct Nread {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int nread;
} nread = {
    PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_COND_INITIALIZER,
    0
};

void *produce(void *arg) {
    for( ; ; ) {
        pthread_mutex_lock(&shared.mutex);
        if (shared.sum >= MAXBUFLEN) {
            pthread_mutex_unlock(&shared.mutex);
            return(NULL);
        }
        shared.buf[shared.sum] = shared.sum; // produce data 1 in shared.buf[shared.sum]
        shared.sum++;
        pthread_mutex_unlock(&shared.mutex);

        pthread_mutex_lock(&nread.mutex);
        if (0 == nread.nread) {
            pthread_cond_signal(&nread.cond);
        }
        nread.nread += 1; 
        pthread_mutex_unlock(&nread.mutex);

        *((int *)arg) += 1;
        sleep(0);
    }
}

void *comsume(void *arg) {
    (void) arg;
    for (int i = 0;i < MAXBUFLEN;i++) {
        pthread_mutex_lock(&nread.mutex);
        while (0 == nread.nread) {
            pthread_cond_wait(&nread.cond, &nread.mutex);
        }
        if (i != shared.buf[i]) {
            printf("buf[%d] = %d\n", i, shared.buf[i]);
        } 
        nread.nread -= 1;
        pthread_mutex_unlock(&nread.mutex);
    }
    return (NULL);
}

```

这里面有个问题关于`条件变量`的正确做法讨论  
我是这么理解的，`pthread_cond_wait`、`pthread_cond_signal` 这个本身就是观察者模式  
如果`消费者`任务在`生产者`任务生产之前注册事件`cond`，那么消息就会完全被消费，线程能被`唤醒`  
但如果`消费者`任务在`生产者`任务生产之后注册事件`cond`，那么这个消息就不知道被消费，线程一直在`沉睡`

所以需要引入`0 === nread.nread` 这样一条来保证，`消费者`是否有必要`睡眠`，保证状态上`消息`是"逻辑未丢"的（实际还是丢了）

事例可以看[陈硕的代码](https://github.com/chenshuo/recipes/blob/master/thread/test/Waiter%5Ftest.cc?ref=blog.hacking.pub)

下面贴链接中一种正确的做法分析

```
class Waiter5 : public Waiter
{
 public:
  void wait() override
  {
    pthread_mutex_lock(&mutex_);
    while (!signaled_)
    {
      pthread_cond_wait(&cond_, &mutex_);
    }
    pthread_mutex_unlock(&mutex_);
  }

  void signal() override
  {
    pthread_mutex_lock(&mutex_);
    signaled_ = true;
    pthread_mutex_unlock(&mutex_);
    pthread_cond_signal(&cond_);
  }

 private:
  bool signaled_ = false;
};

```

##### 重点理解下面几点：

1.首先一点要引入可以判断是否应该进入休眠状态的条件（变量），避免"逻辑上丢失信号"，状态持久化保存能力  
2.~~毕竟cond\_是临界资源，代码块是临界区，需要`mutex`保护~~

> 需要mutext其实为了保证数据一致性（串行），见[爆栈网](https://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex?ref=blog.hacking.pub)

```
Process A　　　　　　　　　　　　　　　　Process B

pthread_mutex_lock(&mutex);
while (condition == FALSE)
　　　　　　　　　　　　　　　　　　　　　condition = TRUE;
　　　　　　　　　　　　　　　　　　　　　pthread_cond_signal(&cond);

pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

这样任务A永远都是睡眠  

这里可以仔细思考一下，当ProcessB被mutex lock保护  
1. 

                                      condition = TRUE;
                                      pthread_cond_signal(&cond);
    最小只需保证condition = TRUE被mutex lock保护就行

2.
                                      pthread_cond_signal(&cond);
                                      condition = TRUE;
    那么两行需要都得被保护

```

3.`pthread_cond_wait`等待会释放锁，唤醒时会上锁（很重要，不然会死锁）  
4.当被唤醒时，需要使用`while`判断，因为可能是`spurious wakeups`唤醒

> 基于上面第3条，多个消费者被唤醒时，会去争抢`锁`，没有抢到锁的线程就属于虚假唤醒了

5.`睡眠`跟`IO阻塞`一样是`阻塞`状态，不占用`cpu`资源  
（对于`挂起`这个词，是有争议的，暂时不使用`挂起`，见下文，理解为`等待阻塞`）

##### 线程状态

关于线程状态，个人认为`java`里面是理解最清晰的

```
1. 新建(new)：新创建了一个线程对象。

2. 可运行(runnable)：线程对象创建后，其他线程(比如main线程）调用了该对象的start()方法。该状态的线程位于可运行线程池中，等待被线程调度选中，获取cpu 的使用权 。

3. 运行(running)：可运行状态(runnable)的线程获得了cpu 时间片（timeslice） ，执行程序代码。

4. 阻塞(block)：阻塞状态是指线程因为某种原因放弃了cpu 使用权，也即让出了cpu timeslice，暂时停止运行。直到线程进入可运行(runnable)状态，才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种： 

(一). 等待阻塞：运行(running)的线程执行o.wait()方法，JVM会把该线程放入等待队列(waitting queue)中。

(二). 同步阻塞：运行(running)的线程在获取对象的同步锁时，若该同步锁被别的线程占用，则JVM会把该线程放入锁池(lock pool)中。

(三). 其他阻塞：运行(running)的线程执行Thread.sleep(long ms)或t.join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入可运行(runnable)状态。

5. 死亡(dead)：线程run()、main() 方法执行结束，或者因异常退出了run()方法，则该线程结束生命周期。死亡的线程不可再次复生。

```

##### 阻塞

看一下[wiki](https://en.wikipedia.org/wiki/Process%5Fstate?ref=blog.hacking.pub)上的定义

```
Blocked
A process transitions to a blocked state when it cannot carry on without an external change in state or event occurring.  

```

也就是说如果你写了一个`while(1)`，占用了`cpu`，是不建议使用`阻塞`去描述，而应该是`running`