【Linux】多线程编程 - 同步/条件变量/信号量

目录

一.线程同步

1.什么是线程同步

2.为什么需要线程同步

3.如何实现线程同步

二.条件变量

1.常见接口以及使用

2.wiat/signal中的第二个参数mutex的意义

3.代码验证

三.POSIX信号量

1.概念

2.常见接口以及使用

四.条件变量vsPOSIX信号量


一.线程同步

1.什么是线程同步

同步: 在保证数据安全的情况下, 让线程能按照某种特定顺序访问临界资源, 从而有效避免饥饿问题, 主要为了解决访问临界资源合理性问题

其中, 按照某种特定顺序访问临界资源, 实际上操作系统的调度队列自动帮助我们维护的, 对于我们而言是透明的, 因为如果有线程在等待资源, 那么一定是一个个的被挂起, 自然就有了顺序性

2.为什么需要线程同步

情景一: 假设有某个线程竞争能力比较强, 就会导致其他线程迟迟访问不到临界资源, 造成饥饿问题

情景二: 线程在使用临界资源前必须要检测临界资源是否就绪, 检测这一过程本质上也是访问临界资源, 那么就需要互斥的访问(加锁解锁), 如果临界资源没有准备就绪, 线程就会不断地检测, 即不断地互斥访问, 也就是在不断加锁解锁, 频繁做无效的加锁解锁工作, 是一种极大的资源浪费

情景三: 生活中的例子, 当我们要去商店买东西时, 总是要问一下是否还有货, 如果没有就要改天再来, 那么我们不可能每次问的时候都在亲自跑到商店去问, 而是在第一次问的时候就留好了联系方式, 如果有货的话老板会联系我来拿, 这本质上就是资源已就绪, 老板唤醒了我这个等待中的线程, 让我来访问

3.如何实现线程同步

线程同步的本质上就是: 对临界资源做检查, 如果资源不就绪就等待, 资源一但就绪就将等待中的线程唤醒, 从而高效有序的进行访问, 避免不必要的锁资源浪费以及饥饿问题

对临界资源检查, 本质也是在访问临界资源, 所以也要互斥进行(信号量不需要, 后续会解释)

如果通过编码的方式实现线程同步:

1.条件变量

2.POSIX信号量

二.条件变量

本质上条件变量就是一个"等待-唤醒"的过程

1.常见接口以及使用

需要包头文件<pthread.h>

类型:

pthread_cond_t

定义方式:

与pthread_mutex_t的定义方式一样 (互斥量mutex详解, 传送入口: http://t.csdn.cn/ikHAk)

定义为全局 or 定义为静态: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

定义为局部:

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

       int pthread_cond_destroy(pthread_cond_t *cond);

使用方式:

等待

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

唤醒

int pthread_cond_broadcast(pthread_cond_t *cond); (广播, 唤醒全部正在等待线程)
int pthread_cond_signal(pthread_cond_t *cond); (只唤醒一个)

2.wiat/signal中的第二个参数mutex的意义

对临界资源检查, 本质也是在访问临界资源, 所以也要互斥进行

在使用上, 一定是

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);

pthread_mutex_unlock(&mutex);

-------------------------------------------------

唤醒pthread_cond_signal(&cond);是否互斥进行看具体场景

在wait接口传入锁地址的意义:

如果一个持有锁的线程挂起等待了, 它是持有锁等待的, 会导致其他线程申请不到锁

所以pthread_cond_wait接口的内部实现, 是先解锁, 等到被唤醒的时候再加锁, 这样的一个设计

所以就需要传入mutex地址

3.代码验证

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>

const static size_t THREAD_NUM = 3;

typedef void (*func_t)(const std::string &, pthread_mutex_t *, pthread_cond_t *);

volatile bool quit = false;

struct ThreadData
{
    ThreadData(const std::string &name, func_t func, pthread_mutex_t *mutex, pthread_cond_t *cond)
        : _name(name), _func(func), _mutex(mutex), _cond(cond)
    {
    }

    std::string _name;
    func_t _func;
    pthread_mutex_t *_mutex;
    pthread_cond_t *_cond;
};

void func1(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func1" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void func2(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func2" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void func3(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func3" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void *entry(void *args)
{
    ThreadData *td = (ThreadData *)args;
    td->_func(td->_name, td->_mutex, td->_cond);

    delete td;
    return nullptr;
}

int main()
{
    pthread_t t[THREAD_NUM];
    func_t funcArr[THREAD_NUM] = {func1, func2, func3};

    pthread_mutex_t mutex;
    pthread_cond_t cond;
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);

    // 创建线程
    // 让这些线程分别执行自己的函数
    for (size_t i = 0; i < THREAD_NUM; ++i)
    {
        std::string str = "thread ";
        str += std::to_string(i + 1);
        ThreadData *td = new ThreadData(str, funcArr[i], &mutex, &cond);
        pthread_create(t + i, nullptr, entry, (void *)td);
    }

    std::cout << "3秒后, 随机单独唤醒10次, 每次一秒" << std::endl;
    sleep(3);
    size_t count = 10;
    for (size_t i = 0; i < count; ++i)
    {
        std::cout << "正在唤醒中..." << std::endl;
        // pthread_cond_signal(&cond);
        pthread_cond_broadcast(&cond);
        sleep(1);
    }

    std::cout << "测试结束" << std::endl;

    quit = true;
    // 最后广播一次, 将所有线程再唤醒一次判断结束
    pthread_cond_broadcast(&cond);

    // 回收线程
    for (size_t i = 0; i < THREAD_NUM; ++i)
    {
        pthread_join(t[i], nullptr);
        std::cout << "线程: " << i + 1 << "已被回收" << std::endl;
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

三.POSIX信号量

1.概念

POSIX信号量和System V信号量作用相同, 都是用于同步的访问共享资源的; 不同的是, POSIX可以用于线程同步

POSIX信号量的本质: 就是一个具有原子性的计数器, 其原子性由库给我们提供好了

对应的两套操作:

P操作 - 本质上是申请, 计数器--

V操作 - 本质上是释放, 计数器++

本质上POSIX信号量更像是一种对资源的预定机制

2.常见接口以及使用

需要包头文件<semaphore.h>

类型:

sem_t

定义方式:

int sem_init(sem_t* sem, int pshared, unsigned int value);

int sem_destory(sem_t* sem);

参数解释:

        pshared: 0表示线程间共享, 非0表示进程间共享

        value: 信号量初始值

使用方式:

P操作

int sem_wait(sem_t* sem);

V操作

int sem_post(sem_t* sem);

四.条件变量vsPOSIX信号量

实现线程同步可以有很多种方式

其中1.条件变量 2.POSIX信号量

那么什么时候用条件变量?什么时候用POSIX信号量呢?

实际上, 条件变量更倾向于不知道临界资源具体有多少, 如果临界资源不够了, 通过统一"等待"的方式来等待临界资源就绪; 而POSIX信号量, 是一种对临界资源的预定机制, 需要明确知道还有多少临界资源可供分配, 才能对计数器做具体的"-- or ++"操作, 即PV操作

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值