歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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\n",__func__, g_val);
}
void * test2(void* args)
{
    sleep(1);
    printf("in %s: g_val = %d\n",__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\n",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\n",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("\n");
}

//消費者
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\n");
            // pthread_cond_wait(&cond,&lock);
            // break;
        }
        else{
        printf("producter is ready...\n");
        pop_front(head,&data);
        printf("%s  data = %d \n",__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\n",__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("\n");
}

//消費者
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\n\n");
            pthread_cond_wait(&cond,&lock);
            break;
        }
        // else{
        // printf("producter is ready...\n");
        pop_front(head,&data);
        printf("%s  data = %d \n",__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\n",__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並返回

Copyright © Linux教程網 All Rights Reserved