【Linux】线程总结:线程同步 -互斥锁,条件变量,信号量实现多生产者多消费者模型

学习环境 :  Centos6.5 Linux 内核 2.6

Linux线程部分总结分为两部分:(1)线程的使用 ,(2)线程的同步与互斥。

第一部分线程的使用主要介绍,线程的概念,创建线程,线程退出,以及线程的终止与分离。【完成】 地址:【Linux】线程总结:初识、创建、等待、终止、分离

第二部分主要介绍在多线程环境下,使用同步与互斥保护共享资源,有互斥锁,条件变量,信号量,以及读写锁。

第二部分开始 (第二部分,拖了快三个月,终于要出生了)

互斥量

描述

互斥变量用来保证同一时刻只有一个线程访问需要保护的代码,用来保证临界资源的数据一致性。本质上可理解为是一把锁,在访问共享资源之前申请锁,只有你一个拿着钥匙,别人要进入就必须等你出去后把钥匙交给他们才可以。一旦使用完毕就要释放锁资源,给别人一个机会。 在使用的时候要注意,申请锁资源与释放锁资源的顺序,避免死锁的产生。

接口


#include
/* 动态初始化 */
int pthread_mutex_init(       pthread_mutex_t* mutex, 
                        const pthread_mutexattr_t* mutexattr);
/* 销毁 */
int pthread_mutex_destroy( pthread_mutex_t* mutex);
/* 申请锁资源 */
int pthread_mutex_lock   ( pthread_mutex_t* mutex);
/* 非阻塞申请锁资源 */
int pthread_mutex_trylock( pthread_mutex_t* mutex);
/* 释放锁资源 */
int pthread_mutex_unlock ( pthread_mutex_t* mutex);

上述接口的第一个参数 mutex 都是定义的 pthread_mutex_t结构体指针,用来操作目标互斥锁。

互斥锁在使用之前必须初始化,pthread_mutex_init函数用于动态初始化互斥锁,mutexattr 参数指定互斥锁的属性。还可以静态分配,设置为常量 PTHREAD_MUTEX_INITALIZER。如果是动态初始化则在使用完成后需要调用pthread_mutex_destory释放资源。

pthread_mutex_lock函数以原子操作的方式给一个互斥锁加锁。如果目标互斥锁已经被锁上,该函数的其余调用者将被阻塞,直到互斥锁的占有者将其解锁。

pthread_mutex_trylock函数非阻塞式加锁,如果已经被加锁时,该函数立即返回错误码 EBUSY。

pthread_mutex_unlock函数以原子操作的方式的给一个互斥锁解锁。如果此时有其他线程在等待这个互斥锁,解锁后将会有一个线程获得该锁。

具体使用方法可参照博客:线程安全与线程不安全,有一个互斥量的简单示例。

条件变量

互斥锁用于同步线程对共享数据的访问,而条件变量用于在线程之间同步共享数据的值,给多个线程提供了一个会合的场所。

条件变量本身是由互斥量保护的。线程在改变条件状态之前必须先锁住。

接口

#include 

/* 动态初始化 */
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
/* 销毁资源*/
int pthread_cond_destroy(pthread_cond_t* cond);
/* 等待cond 条件发生 */
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
/* 通知 cond 条件发生,只通知一个线程 */
int pthread_cond_signal(pthread_cond_t* cond);
/* 通知cond条件发生,通知所有进程 */
int pthread_cond_broadcast(pthread_cond_t* cond);

静态初始化, 设置宏 PTHREAD_COND_INITIALIZER

使用实例:基于互斥锁和条件变量单缓冲区(一次只能有一个对象操作)多生产者多消费者模型

有一个链表,限制最多可插入10个结点,
一个互斥锁保护对链表的操作,
两个条件变量通知满 10 以及 为空。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>

/* 静态初始化 锁 和 条件变量*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty  = PTHREAD_COND_INITIALIZER;

#define MAXSIZE  10
int count = 0;

typedef struct node
{
    int data;
    struct node* next;
} node_t, *pnode_t, **ppnode_t;


/* 初始化链表的头结点 */
void list_init(ppnode_t pphead)
{
    assert(pphead);
    *pphead = (pnode_t)malloc(sizeof(node_t));
    (*pphead)->next = NULL;
}

/* 头插 */
void list_push(pnode_t phead, int data)
{
    assert(phead);
    pnode_t temp = (pnode_t)malloc(sizeof(node_t));
    if(temp){
        temp->data = data;
        temp->next = phead->next;
        phead->next = temp;
    }
}

int list_empty(pnode_t phead)
{
    return phead->next == NULL ? 1 : 0;
}
/* 头删并通过参数返回被删除的元素 */ 
void list_pop(pnode_t phead, int* data)
{
    assert(phead);
    if(!list_empty(phead))
    {
        pnode_t temp = phead->next;
        phead->next = temp->next;
        *data = temp->data;
        free(temp);
    }
}

void list_clear(pnode_t phead)
{
    while(!list_empty)
    {
        int data;
        list_pop(phead, &data);
    }
}

void list_destory(ppnode_t phead)
{
    assert(phead);
    list_clear(*phead);
    free(phead);
    phead = NULL;
}


void list_print(pnode_t phead)
{
    pnode_t start = phead->next;
    while(start)
    {
        printf("%d ", start->data);
        start = start->next;
    }
    printf("\n");
}

void* producer(void* arg)
{
    pnode_t phead = (pnode_t)arg;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while(count == MAXSIZE)
        {
            pthread_cond_wait(&not_full ,&mutex);
        }
        int data = 0;
        data =  rand()%100;
        list_push(phead,data);
        printf("producer : %d\n", data);
        count++;
        pthread_cond_signal(&not_empty);
        pthread_mutex_unlock(&mutex);
    }

}
void* consumer(void* arg)
{
    pnode_t phead = (pnode_t)arg;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while(count == 0)
        {
             pthread_cond_wait(&not_empty, &mutex);
        }

        int data = 0;
        list_pop(phead, &data);
        printf("consumer: %d \n", data);
        count--;
        pthread_cond_signal(&not_full);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}


int main()
{
    pnode_t phead;
    list_init(&phead);

    //pthread_mutex_init(&mutex, NULL);
    //pthread_cond_init(&cond, NULL);
    //pthread_cond_init(&empty, NULL);
    //
    /* 创建生产者线程和消费者线程 */
    pthread_t id1, id2, id3, id4;
    pthread_create(&id1, NULL, producer, (void*)phead);
    pthread_create(&id2, NULL, producer, (void*)phead);
    pthread_create(&id3, NULL, consumer, (void*)phead);
    pthread_create(&id4, NULL, consumer, (void*)phead);

    /* 线程等待回收资源 */
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    pthread_join(id3, NULL);
    pthread_join(id4, NULL);

    /* 销毁锁和条件变量资源 */
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&not_empty);
    pthread_cond_destroy(&not_full);

    return 0;
}

【Linux】线程总结:线程同步 -互斥锁,条件变量,信号量实现多生产者多消费者模型_第1张图片

信号量

接口:

#include 

/*初始化信号量, pshared 为0表示在同一进程的线程间同步,value 指定信号量的初始值*/
int sem_init(sem_t* sem, int pshared, unsigned int value);
/* 释放信号量占有的内核资源 */
int sem_destory (sem_t* sem);
/* 让信号量-1 相当于 P 操作 申请资源 */
int sem_wait    (sem_t* sem);
/* sem_wait 的非阻塞版本 */
int sem_trywait (sem_t* sem);
/* 让信号量+1 相当于 V 操作 释放资源*/
int sem_post    (sem_t* sem);

使用实例:基于环形队列的多生产者多消费者模型:

#include 
#include 
#include 


/* 环形队列可分配资源数目 */
#define RESOURCE 6

int queue[RESOURCE];
int in = 0;
int out = 0;


/* 可生产数目信号量 , 初始值为环形队列大小*/
sem_t sem_p;
/* 可消费数目信号线 , 初始值为 0  */
sem_t sem_c;

/* 生产者与生产者之间的锁 */
pthread_mutex_t mutex_p = PTHREAD_MUTEX_INITIALIZER;
/* 消费者与消费者之间的锁 */
pthread_mutex_t mutex_c = PTHREAD_MUTEX_INITIALIZER;

/* 生产者数目,消费者数目*/
#define PRODUCER 5
#define CONSUMER 5


void* producer(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_p);
        sem_wait(&sem_p);
        printf("producer begin ...\n");
        queue[in] = rand()%100;
        int data = queue[in];
        in = (in+1) % RESOURCE;
        printf("producer %d  [done] \n", data);
        sem_post(&sem_c);
        pthread_mutex_unlock(&mutex_p);
    }
}
void* consumer(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_c);
        sem_wait(&sem_c);
        printf("consumer begin ...\n");
        int data = queue[out];
        queue[out] = -1;
        out = (out+1) % RESOURCE;
        sleep(1);
        printf("consumer %d [done]\n", data);
        sem_post(&sem_p);
        pthread_mutex_unlock(&mutex_c);
    }
}

int main()
{

    sem_init(&sem_p, 0, RESOURCE);
    sem_init(&sem_c, 0, 0);
    pthread_mutex_init(&mutex_c, NULL);
    pthread_mutex_init(&mutex_p, NULL);
    pthread_t p[10];
    pthread_t c[10];

    /* 创建线程 */
    int i = 0;
    for(; i < PRODUCER; i++){
        pthread_create(&p[i], NULL, producer, NULL);
    }
    i = 0;
    for(; i < CONSUMER; i++){
        pthread_create(&c[i], NULL, consumer, NULL);
    }


    /* 回收线程  */
    i = 0;
    for(; i < PRODUCER ; i++){
        pthread_join(p[i], NULL);
    }
    i = 0;
    for(; i < CONSUMER ; i++){
        pthread_join(c[i], NULL);
    }

    sem_destroy(&sem_p);
    sem_destroy(&sem_c);
    pthread_mutex_destroy(&mutex_p);
    pthread_mutex_destroy(&mutex_c);
    return 0;

}

【Linux】线程总结:线程同步 -互斥锁,条件变量,信号量实现多生产者多消费者模型_第2张图片

扩展

还有读写锁,用来处理读者与写者模型。读者与写者互斥与同步关系,读者之间没有关系,写者之间互斥。

第二部分完

你可能感兴趣的:(linux)