zoukankan      html  css  js  c++  java
  • linux多线程-互斥&条件变量与同步

    多线程代码问题描述

    我们都知道,进程是操作系统对运行程序资源分配的基本单位,而线程是程序逻辑,调用的基本单位。在多线程的程序中,多个线程共享临界区资源,那么就会有问题:

    比如

    #include <pthread.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int g_val = 10;
    void * test1(void* args)
    {
        g_val = 20;
        printf("in %s: g_val = %d
    ",__func__, g_val);
    }
    void * test2(void* args)
    {
        sleep(1);
        printf("in %s: g_val = %d
    ",__func__,g_val);
    }
    int main(int argc, char const *argv[])
    {
        pthread_t id1,id2;
        pthread_create(&id1,NULL,test1,NULL);
        pthread_create(&id2,NULL,test2,NULL);
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
        return 0;
    }

    由次我们可以看到,线程1修改了全局变量,而线程2中页跟着改变了。

    那么,对于这个问题进行放大,我们就会找到多线程存在的问题。如下

    #include <stdio.h>
    #include <pthread.h>
    // pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    int g_val = 0;
    void* add(void *argv)
    {
        for(int i = 0 ; i < 5000; ++i)
        {
            // g_val++;
            // pthread_mutex_lock(&lock);
            int tmp = g_val;
            g_val = tmp+1;
            // pthread_mutex_unlock(&lock);
        }
    }
    
    int main(int argc, char const *argv[])
    {
        pthread_t id1,id2;
    
        pthread_create(&id1,NULL,add,NULL);
        pthread_create(&id2,NULL,add,NULL);
    
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
    
        printf("%d
    ",g_val);
        return 0;
    }

    在上面代码中,我们执行两个线程分别对全局变量累加5000次,但是得到的结果却是不确定的。这是因为,在多线程程序中,线程调度使得线程间进行切换执行,如果当线程1将数据从内存读入cpu正在准备累加时,调度器切换线程2执行,此时,线程2获取的值是未累加的。那么,当两个线程都执行完本次累加后,实际值只增加了1。所以就会产生多次执行,结果不确定性。

    注:代码中没有直接g_val++,而选择了tmp过度就是为了产生非原子操作,让调度过程处于累加未完时。

    那么解决这个问题,就需要互斥操作了。

    我们首先来谈互斥量mutex

    通过互斥量实现线程锁,在每个线程累加之前,进行临界资源的锁操作,在结束时解锁,那么就能保证目标的实现了。

    #include <stdio.h>
    #include <pthread.h>
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    int g_val = 0;
    void* add(void *argv)
    {
        for(int i = 0 ; i < 5000; ++i)
        {
            // g_val++;
            pthread_mutex_lock(&lock);
            int tmp = g_val;
            g_val = tmp+1;
            pthread_mutex_unlock(&lock);
        }
    }
    
    int main(int argc, char const *argv[])
    {
        pthread_t id1,id2;
    
        pthread_create(&id1,NULL,add,NULL);
        pthread_create(&id2,NULL,add,NULL);
    
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
    
        printf("%d
    ",g_val);
        return 0;
    }

    关于互斥锁的实现,在linux中实现如下

    条件变量

    问题场景描述

    假设我们现在需要做一个生产者消费者模型,生产者对带有头节点的链表头插方式push_front生产数据,消费者调用pop_front消费数据.而生产者可能动作比较慢,这时就会有问题。

    生产者生产一个数据时间,消费者可能迫切需求。因此,一直轮寻申请锁资源,以便进行消费。所以就会产生多次不必的锁资源申请释放动作。影响系统性能。

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    typedef struct node 
    {
        int _data;
        struct node *_next;
    }node_t,* node_p,**node_pp;
    
    node_p head = NULL;
    
    node_p alloc_node(int data)
    {
        node_p ret = (node_p)malloc(sizeof(node_t));
        ret->_data = data;
        ret->_next = NULL;
        return ret;
    }
    
    void init(node_pp phead)
    {
        *phead = alloc_node(0);
    }
    
    void push_front(node_p head,int data)
    {
        node_p tmp = alloc_node(data);
        tmp->_next = head->_next;
        head->_next = tmp;
    }
    
    void pop_front(node_p head, int * pdata)
    {
        if(head->_next!=NULL)
        {
            node_p tmp = head->_next;
            head->_next = tmp->_next;
    
            *pdata = tmp->_data;
            free(tmp);        
        }
    }
    
    void show(node_p head)
    {
        node_p cur = head->_next;
        while(cur)
        {
            printf("%d->", cur->_data);
            cur = cur->_next;
        }
        printf("
    ");
    }
    
    //消费者
    void * consumer(void *argv)
    {
        int data;
        while(1)
        {
            pthread_mutex_lock(&lock);
            // while(head->_next==NULL)
            if(head->_next==NULL)
            {
                printf("producter is not ready
    ");
                // pthread_cond_wait(&cond,&lock);
                // break;
            }
            else{
            printf("producter is ready...
    ");
            pop_front(head,&data);
            printf("%s  data = %d 
    ",__func__, data);
            }
            pthread_mutex_unlock(&lock);
    
            sleep(1);
        }
    }
    
    void * producter(void * argv)
    {
        int data = rand()%1234;
        while(1)
        {
            sleep(4);
            pthread_mutex_lock(&lock);
            push_front(head,data);
            printf("%s data :: %d
    ",__func__, data);
            pthread_mutex_unlock(&lock);
            // pthread_cond_signal(&cond);
        }
    }
    
    int main(int argc, char const *argv[])
    {
        init(&head);
    
        pthread_t id1,id2;
        
        pthread_create(&id1,NULL,consumer,NULL);
        pthread_create(&id2,NULL,producter,NULL);
    
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
    }

    由上,我们发现。生产者生叉一个数据之后,消费者总是会多次进行锁资源申请并尝试消费数据。那么,解决这一问题的方案就是:条件变量。

    具体实现如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    typedef struct node 
    {
        int _data;
        struct node *_next;
    }node_t,* node_p,**node_pp;
    
    node_p head = NULL;
    
    node_p alloc_node(int data)
    {
        node_p ret = (node_p)malloc(sizeof(node_t));
        ret->_data = data;
        ret->_next = NULL;
        return ret;
    }
    
    void init(node_pp phead)
    {
        *phead = alloc_node(0);
    }
    
    void push_front(node_p head,int data)
    {
        node_p tmp = alloc_node(data);
        tmp->_next = head->_next;
        head->_next = tmp;
    }
    
    void pop_front(node_p head, int * pdata)
    {
        if(head->_next!=NULL)
        {
            node_p tmp = head->_next;
            head->_next = tmp->_next;
    
            *pdata = tmp->_data;
            free(tmp);        
        }
    }
    
    void show(node_p head)
    {
        node_p cur = head->_next;
        while(cur)
        {
            printf("%d->", cur->_data);
            cur = cur->_next;
        }
        printf("
    ");
    }
    
    //消费者
    void * consumer(void *argv)
    {
        int data;
        while(1)
        {
            pthread_mutex_lock(&lock);
            while(head->_next==NULL)
            // if(head->_next==NULL)
            {
                printf("producter is not ready
    
    ");
                pthread_cond_wait(&cond,&lock);
                break;
            }
            // else{
            // printf("producter is ready...
    ");
            pop_front(head,&data);
            printf("%s  data = %d 
    ",__func__, data);
            // }
            pthread_mutex_unlock(&lock);
    
            sleep(1);
        }
    }
    
    void * producter(void * argv)
    {
        int data = rand()%1234;
        while(1)
        {
            sleep(4);
            pthread_mutex_lock(&lock);
            push_front(head,data);
            printf("%s data :: %d
    ",__func__, data);
            pthread_mutex_unlock(&lock);
            pthread_cond_signal(&cond); //条件变量v操作
        }
    }
    
    int main(int argc, char const *argv[])
    {
        init(&head);
    
        pthread_t id1,id2;
        
        pthread_create(&id1,NULL,consumer,NULL);
        pthread_create(&id2,NULL,producter,NULL);
    
        pthread_join(id1,NULL);
        pthread_join(id2,NULL);
    }

    由图可以看出,这下我们的消费者不再进行过多次没必要的轮寻访问,当生产者生产数据时,告诉消费者可以进行消费了,那么消费者进行消费。

    其实这也就是著名的:好莱坞原则---不要打电话给我们,我们会通知你。

    eg,在面试笔试中,我们不需要过度的紧张是否被录用,只需要在做到最大努力之后等着招聘方通知就好。

    注:一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用
    pthread_cond_wait在一一个Condition Variable上阻塞等待,这个函数做以下三步操作:
    1. 释放Mutex
    2. 阻塞等待
    3. 当被唤醒时,重新获得Mutex并返回

  • 相关阅读:
    js数组
    js中严格模式
    js 中bind
    HDU 2846(Trie树)
    HDU 4825(01 Trie树)
    Codeforces 900B (Java高精度或模拟)
    2018 ACM-ICPC 中国大学生程序设计竞赛线上赛 I Reversion Count(Java大数)
    2018 ACM-ICPC 中国大学生程序设计竞赛线上赛 B Goldbach (素数测试,随机化算法)
    HDU 2256(矩阵快速幂)
    HDU 6029(思维)
  • 原文地址:https://www.cnblogs.com/lang5230/p/5686868.html
Copyright © 2011-2022 走看看