Linux线程(2)--线程同步(2)--条件变量、信号量

1 条件变量

1.1 条件变量

条件变量本身不是锁!但它也可以造成线程阻塞。
通常与互斥锁配合使用。给多线程提供一个会合的场所。



1.2 条件变量的主要相关函数

  • pthread_cond_t 类型 用户定义条件变量
    • pthread_cond_t cond;

1 初始化条件变量

/*
 * function: 初始化一个条件变量
 *
 * function arguments:
 *  argv1: 条件变量
 *  argv2: 条件变量属性,通常为默认值,传NULL
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */

int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);

// 条件变量也可以使用静态初始化的方法:
// pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2 销毁条件变量

/*
 * function: 销毁条件变量
 *
 * function arguments:
 *  argv1: 条件变量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */
int pthread_cond_destroy(pthread_cond_t *cond);

3 阻塞等待一个条件变量

/*
 * function: 阻塞等待一个条件变量
 *              1. 阻塞等待条件变量
 *              2. 释放已掌握的互斥锁  相当于 pthread_mutex_unlock();  (1.2两步为同时操作,为一个原子操作)
 *              3. 当被唤醒时,pthread_cond_wait函数返回时,解除阻塞并且重新申请获取互斥锁pthread_mutex_lock;
 *
 * function arguments:
 *  argv1: 条件变量
 *  argv2: 互斥锁
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */
int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);

4 限时等待一个条件变量

/*
 * function: 限时等待一个条件变量
 *              1. 等待条件变量
 *              2. 释放已掌握的互斥锁  相当于 pthread_mutex_unlock();  (1.2两步为同时操作,为一个原子操作)
 *              3. 当被唤醒时,pthread_cond_wait函数返回时,解除阻塞并且重新申请获取互斥锁pthread_mutex_lock;
 *              4. 如果定时时间到了,返回错误
 *
 * function arguments:
 *  argv1: 条件变量
 *  argv2: 互斥锁
 *  argv3: (可以通过man sem_timedwait函数,查看struct timespec结构体)
 *          struct timespec{
 *              time_t tv_sed;      // 秒
 *              long   tv_nsec;     // 纳秒
 *          }
 *
 *          参3,abstime为绝对时间
 *          如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
 *          struct timespec t = {1, 0};
 *          pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
 *          正确用法:
 *          time_t cur = time(NULL); // 获取当前时间。
 *          struct timespec t;       // 定义 timespec 结构体变量 t
 *          t.tv_sec = cur+1;        // 定时 1 秒
 *          pthread_cond_timedwait (&cond, &mutex, &t); 
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
        pthread_mutex_t *restrict mutex,
        const struct timespec *restrict abstime);

5 唤醒至少一个阻塞在条件变量上的线程

/*
 * function: 唤醒至少一个阻塞在条件变量上的线程
 *
 * function arguments:
 *  argv1: 条件变量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */
int pthread_cond_signal(pthread_cond_t *cond);

6 唤醒全部阻塞在条件变量上的线程

/*
 * function: 唤醒全部阻塞在条件变量上的线程
 *
 * function arguments:
 *  argv1: 条件变量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回错误号
 */
int pthread_cond_broadcast(pthread_cond_t *cond);

1.3 生产者消费者模型

// 实际应用时记得判断pthread系列函数返回值
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定义并初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;         // 定义并初始化条件变量


struct msg 
{
    int num;
    struct msg* next;
};
struct msg* head;    // 全局区的产品

void *producerThread(void *argv)    // 生产者线程
{
    while (1) 
    {   
        // 1.生产产品
        struct msg *tmp = new msg{};
        tmp->num = rand()%1000 + 1;
        printf("product-------%d\n", tmp->num);

        pthread_mutex_lock(&mutex);     // 2.加锁

        // 3.将生产的产品放入全局区
        tmp->next = head;
        head = tmp;

        pthread_mutex_unlock(&mutex);   // 4.解锁

        pthread_cond_signal(&cond);     // 5.唤醒等待此条件变量的线程

        sleep(rand()%3);
    }   

    pthread_exit(NULL);
}

void *consumerThread(void *argv)    // 消费者线程
{   
    while (1)
    {   
        struct msg *tmp;
        
        pthread_mutex_lock(&mutex);     // 1.加锁
        
        // 2.等待产品被生产出来(当全局区产品为空时,等待有产品被生产出)
        while (head == NULL)                    // 对于一个生产者多个消费者,此处不能使用if(因为有可能当被唤醒时,产品已经被别的消费者使用,需要重新判断是否有还有产品)
        {                                    
            pthread_cond_wait(&cond, &mutex);
        }
        
        // 3.模拟消费一个产品
        tmp = head;
        head = tmp->next;
        printf("consume------------%d\n", tmp->num);
        
        pthread_mutex_unlock(&mutex);   // 4.解锁
        
        delete tmp;
        
        sleep(rand()%3);
    }
    
    pthread_exit(NULL);
}

int main()
{
    srand(time(NULL));
    pthread_t pID, cID;

    int ret = pthread_create(&pID, NULL, producerThread, NULL);     // 创建生产者线程
    if (0 != ret)
    {
        printf("pthread_create producerThread error:%s\n", strerror(ret));
        return -1;
    }

    ret = pthread_create(&pID, NULL, consumerThread, NULL);     // 创建消费者线程
    if (0 != ret)
    {
        printf("pthread_create consumerThread error:%s\n", strerror(ret));
        return -1;
    }

    // 回收生产者、消费者线程
    pthread_join(pID, NULL);
    pthread_join(cID, NULL);

	// 销毁互斥锁、条件变量
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);

    pthread_exit(NULL);
}


2 信号量

2.1 信号量

互斥锁为初始值为1。

信号量相当于初始值为N的互斥量。N值,表示可以同时访问共享数据区的线程数。

2.2 信号量的主要相关函数

  • sem_t 类型 用户定义条件变量
    • 本质是结构体。但应用期间可简单看作为整数,忽略实现细节
    • sem_t sem;
    • 规定信号量 sem 不能 < 0
    • 头文件 <semaphore.h>

1 初始化

/*
 * function: 初始化一个信号量
 *
 * function arguments:
 *  argv1: 信号量
 *  argv2: 0-用于线程间,非0-用于进程间
 *  argv3: 指定信号量的初值(前文信号量概念中的N)
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_init(sem_t *sem, int pshared, unsigned int value);

2 销毁信号量

/*
 * function: 销毁一个信号量
 *
 * function arguments:
 *  argv1: 信号量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_destroy(sem_t *sem);

3 加锁

/*
 * function: 给信号量加锁 --操作
 *
 * function arguments:
 *  argv1: 信号量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_wait(sem_t *sem);

4 解锁

/*
 * function: 给信号量解锁 ++操作
 *
 * function arguments:
 *  argv1: 信号量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_post(sem_t *sem);

5 尝试加锁

/*
 * function: 尝试对信号量加锁 --操作 (与 sem_wait 的区别类比 lock 和 trylock)
 *
 * function arguments:
 *  argv1: 信号量
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_trywait(sem_t *sem);

6 限时加锁

/*
 * function: 限时尝试对信号量加锁 --操作
 *
 * function arguments:
 *  argv1: 信号量
 *  argv2: (可以通过man sem_timedwait函数,查看struct timespec结构体)
 *          struct timespec{
 *              time_t tv_sed;      // 秒
 *              long   tv_nsec;     // 纳秒
 *          }
 *
 *          参2,abs_timeout为绝对时间
 *          如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
 *          struct timespec t = {1, 0};
 *          sem_timedwait(&sem, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
 *          正确用法:
 *          time_t cur = time(NULL); // 获取当前时间。
 *          struct timespec t;       // 定义 timespec 结构体变量 t
 *          t.tv_sec = cur+1;        // 定时 1 秒
 *          sem_timedwait(&sem, &t);
 *
 * return value:
 *  success: 返回0
 *  faild: 返回-1,并设置errno
 */
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

2.3 加锁和解锁

sem_wait:       1. 信号量大于 0,则信号量-- (类比 pthread_mutex_lock)
	|			2. 信号量等于 0,造成线程阻塞
   对应
    |
sem_post:      将信号量++,同时唤醒阻塞在信号量上的线程 (类比 pthread_mutex_unlock)

但,由于 sem_t 的实现对用户隐藏,所以所谓的++、 --操作只能通过函数来实现,而不能直接++、 --符号。
信号量的初值,决定了占用信号量的线程的个数。

2.4 生产者消费者模型

// 信号量实现生产者消费者模型
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>

#define NUM 5

int queue[NUM] = {0};       // 全局数组实现环形队列

// 定义全局的信号量(空格子信号量、产品信号量)
sem_t g_blank, g_product;


void *producerThread(void *argv)    // 生产者线程
{
    int i = 0;

    while (1)
    {
        sem_wait(&g_blank);                         // 生产者将空格子数--,为0则阻塞等待
        queue[i] = rand()%1000 + 1;                 // 生产一个产品
        printf("product-------%d\n", queue[i]);
        sem_post(&g_product);                       // 将产品数++

        i = (i+1) % NUM;                            // 借助下标实现环形队列
        sleep(rand()%3);
    }   

    pthread_exit(NULL);
}

void *consumerThread(void *argv)    // 消费者线程
{
    int i = 0;

    while (1) 
    {   
        sem_wait(&g_product);                           // 消费者将产品数--,为0则阻塞等待
        printf("consume------------%d\n", queue[i]);    // 消费一个产品
        queue[i] = 0;
        sem_post(&g_blank);                             // 消费掉以后,将空格子数++


        i = (i+1) % NUM;
        sleep(rand()%3);
    }

    pthread_exit(NULL);
}

int main()
{
    srand(time(NULL));

    // 初始化信号量
    sem_init(&g_blank, 0, NUM);     // 初始化空格子信号量为5
    sem_init(&g_product, 0, 0);     // 最开始产品数为0

    pthread_t pID, cID;
    int ret = pthread_create(&pID, NULL, producerThread, NULL);     // 创建生产者线程
    if (0 != ret)
    {
        printf("pthread_create producerThread error:%s\n", strerror(ret));
        return -1;
    }

    ret = pthread_create(&pID, NULL, consumerThread, NULL);         // 创建消费者线程
    if (0 != ret)
    {
        printf("pthread_create consumerThread error:%s\n", strerror(ret));
        return -1;
    }

    // 回收生产者、消费者线程
    pthread_join(pID, NULL);
    pthread_join(cID, NULL);

    // 销毁信号量
    sem_destroy(&g_blank);
    sem_destroy(&g_product);

    pthread_exit(NULL);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值