多线程编程介绍-条件变量

多线程编程介绍-条件变量

条件变量定义

条件变量是多线程对共享资源数据的变化的通知机制。条件变量与互斥量明显不一样为互斥量是对临界资源的保护机制,但条件变量能够理解为一种通讯机制。html

条件变量的应用场景

设想以下编程场景,咱们要实现一个消息接收转发并处理的流程,为了提升程序执行效率。咱们启动两个线程一个是接收消息线程,专门负责接收消息,将消息加入到一个共享链表中;而一个线程是工做线程,专门负责等待读取链表中的消息,若是链表为空,则工做线程则进入等待队列,若是有节点插入则工做线程须要被唤醒继续工做。编程

  • 问题1:共享资源消息链表须要保护,怎么作?多线程

    互斥量是解决共享资源的典型方法,相信你们都知道,使用mutex锁保护一下对链表的操做就行了。对于链表操做的代码段咱们成为临界资源。函数

  • 问题2:如何通知工做线程有消息到达?操作系统

    方法1:你们应该都能想到,利用现有知识互斥量,工做线程一直轮询去检查链表中是否有消息能够处理,不就能够了,但这样显然效率过低,浪费cpu资源。线程

    方法2:当有资源时接收消息线程通知工做线程一下不就能够了,其余时间工做线程就休眠就好啦。是的,条件变量就是为了达到这个目的的。当工做线程检查没有消息处理时,就主动将本身挂起,等待接收消息线程唤醒。rest

条件变量功能函数介绍

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • pthread_cond_init初始化一个条件变量,须要在其余条件变量执行函数以前执行。code

  • 只能被初始化一次,若是初始化两次则结果不可预期。htm

  • restrict cond 是一个条件变量地址,使用此函数前须要定义一个条件变量结构体pthread_cond_tblog

  • 若是须要copy条件变量,只能copy条件变量地址,不能复制这个结构体,不然结果将不可预知。(想一想也知道这个条件变量是一个结构体,若是复制一个结构体则信息被复制两份在多线程中必定会出现问题,而复制地址则原内容不变)

  • 第二个字段属性字段,通常默认填NULL(其余高级用法待研究,若是有知道的小伙伴还望分享一下啊~)

    int pthread_cond_destroy(pthread_cond_t *cond);
  • 释放一个条件变量,通常值只在线程结束时调用,用于回收条件变量资源。

    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
    const struct timespec *restrict abstime);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 这两个函数用于主动将线程阻塞,等待其余线程唤醒。对应场景中工做线程判断没有能够获取的消息时将线程主动放入阻塞队列中(这里的阻塞队列指操做系统中的),即线程将本身挂起。

  • 函数中前两个参数一个是条件变量自己,一个是mutex互斥量。条件变量没什么好说的,由于这组函数就是和他相关的,那mutex呢?mutex是用来保护临界资源使用的mutex,如场景中所说的对于消息队列的访问是须要加锁保护的。之因此要出入mutex是由于线程在阻塞挂起本身以前要先释放锁,否则其余线程也不能获取锁了。

  • 根据上一点分析mutex不但要做为入参传入,并且须要在传入以前先得到锁。

  • 固然pthread_cond_timedwait函数的最后一个参数能够指定阻塞的时间,即即便在指定的时间没没有线程唤醒这个阻塞线程,阻塞线程也会本身被唤醒执行。

    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);
  • 以上这两个函数是用来向等待此条件变量的线程发送信号,表示能够继续运行了。对应咱们的场景就是接收线程有消息到达,以后将消息插入队列,通知工做线程。

  • 两个函数不一样之处,pthread_cond_broadcast唤醒等待中的全部线程。pthread_cond_signal至少唤醒一个等待线程。

  • 若是执行这两个函数的时候没有任何等待此条件变量的线程,则无任何影响,也无任何变化。这一点在后续问题介绍时会再次提到。

条件变量编程实例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

#define MSG_LEN_MAX        31

typedef struct tagNode
{
    char szMsg[MSG_LEN_MAX+1];
    struct tagNode *pstNextNode;
}NODE_S;

typedef struct tagList
{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    NODE_S stHead;
}LIST_S;

LIST_S g_stMsgList = {};

void list_Init(LIST_S *pstList)
{
    pstList->stHead.pstNextNode = NULL;
    pthread_mutex_init(&pstList->mutex, NULL);
    pthread_cond_init(&pstList->cond, NULL);
}
/*工做线程用于处理并打印消息*/
void * dealMsgThread(void * pVoid)
{
    NODE_S *pstNode = NULL;
    int count = 0;
    
    while (1)
    {
        pthread_mutex_lock(&g_stMsgList.mutex);        
        
        /* 查找链表中的节点的头节点返回 */
        if ( NULL == g_stMsgList.stHead.pstNextNode )
        {
            printf("can not find msg,waiting...\n");
            pthread_cond_wait(&g_stMsgList.cond, &g_stMsgList.mutex);
            printf("notify msg, wake up\n");
        }

        if ( NULL != g_stMsgList.stHead.pstNextNode )
        {
            pstNode = g_stMsgList.stHead.pstNextNode;
            g_stMsgList.stHead.pstNextNode = pstNode->pstNextNode;
            printf("echo: %s\n", pstNode->szMsg);
            free(pstNode);
            count++;
        }

        pthread_mutex_unlock(&g_stMsgList.mutex);
        if ( count >= 10 )
        {
            break;
        }
    }

    return NULL;
}
/*向消息队列中放入节点,并通知等待的工线程*/
void receiveMsg(char *pcMsg, int iLen)
{
    NODE_S *pstNode = NULL;
    NODE_S* pstTemp = NULL;
    
    if ( iLen > MSG_LEN_MAX )
    {
        iLen = MSG_LEN_MAX;
    }

    pstNode = (NODE_S*)malloc(sizeof(NODE_S));
    memset(pstNode, 0, sizeof(NODE_S));
    snprintf(pstNode->szMsg, MSG_LEN_MAX+1, "%s", pcMsg);

    /* 获取锁 */
    pthread_mutex_lock(&g_stMsgList.mutex);

    pstTemp = &g_stMsgList.stHead;
    while ( pstTemp )
    {
        if ( NULL == pstTemp->pstNextNode )
        {
            break;
        }

        pstTemp = pstTemp->pstNextNode;
    }

    pstTemp->pstNextNode = pstNode;
    
    printf("recieve msg %s add list, send signal\n", pcMsg);
    /* 发送通知到工做线程 */
    pthread_cond_signal(&g_stMsgList.cond);

    pthread_mutex_unlock(&g_stMsgList.mutex);

    return;
}
/*main函数*/
int main(int argc, char **argv)
{
    pthread_t iThreadId;
    void *ret = NULL;
    char szMsg[MSG_LEN_MAX+1];
    
    list_Init(&g_stMsgList);
    
    pthread_create(&iThreadId, NULL, dealMsgThread, NULL);
    //sleep(1);
    for(int i =0 ; i < 10; i++)
    {
        sprintf(szMsg, "%d : hello", i);
        receiveMsg(szMsg, strlen(szMsg));
    }
    
    pthread_join(iThreadId, &ret);

    return 0;
}
  • 输出结果:

    • recieve msg 0 : hello add list, send signal
      recieve msg 1 : hello add list, send signal
      recieve msg 2 : hello add list, send signal
      recieve msg 3 : hello add list, send signal
      recieve msg 4 : hello add list, send signal
      recieve msg 5 : hello add list, send signal
      recieve msg 6 : hello add list, send signal
      recieve msg 7 : hello add list, send signal
      echo: 0 : hello
      echo: 1 : hello
      echo: 2 : hello
      echo: 3 : hello
      echo: 4 : hello
      echo: 5 : hello
      echo: 6 : hello
      echo: 7 : hello
      can not find msg,waiting...
      recieve msg 8 : hello add list, send signal
      recieve msg 9 : hello add list, send signal
      notify msg, wake up
      echo: 8 : hello
      echo: 9 : hello

条件变量函数内部实现猜测

1.条件变量内部猜测一:条件变量pthread_cond_timedwait函数内部对于条件变量自己还存在一个锁。

  • 这个锁的用途就是保证条件变量挂起和释放锁是一个原子操做。

  • 设想场景,若是进入pthread_cond_timedwait函数以后,先释放mutex(必须执行释放操做,前文提到),以后再进入等待挂起以前,cpu切换到执行pthread_cond_signal线程,此时因为没有等待线程,从而不会有任何影响,这就致使后续进入wait态的工做线程永远等不到被接收线程唤醒。因此为保证释放和进入wait态不能被打断,因此须要加锁保护。

  • 固然不仅是猜测,经过一篇内核态对于pthread_cond_timedwait函数的分析也证明了这一点。连接:https://www.cnblogs.com/c-slm...

条件变量使用注意事项

  • 条件变量使用前初始化,且初始化一次。

  • 条件变量使用pthread_cond_timedwait类函数时须要在获取mutex锁和释放mutex锁之间。

  • 使用pthread_cond_timedwait函数被唤醒后仍然须要判断队列中的状态,由于可能被其余线程首先抢占了mutex并处理了消息队列消息。即等待的条件变量失效,须要从新等待。

  • 条件变量使用pthread_cond_signal类函数时能够在获取mutex锁和释放mutex锁之间,也能够在获取mutex锁和释放mutex锁以后。但建议在获取mutex锁和释放mutex锁之间。

    • pthread_cond_signal函数在mutex lock与unlock之间执行。

      • 缺点:可能致使pthread_cond_wait线程执行后从新进入休眠,由于wait线程须要获取mutex锁,但此时signal线程可能并无释放,致使频繁的cpu切换。

    • pthread_cond_signal函数在mutex lock,unlock以后执行。

      • 缺点:先unlock操做以后此时低优先级任务可能会占用cpu资源致使wait的高优先级任务得不到调度。由于wait的函数尚未收到signal信号唤醒。

相关文章
相关标签/搜索