当前位置: 首页 > wzjs >正文

鹰潭网站建设公司租用大型服务器多少钱

鹰潭网站建设公司,租用大型服务器多少钱,自动搭建网站,网站设计毕业设计一、背景概念 临界资源:多线程执行流共享的资源(同一时刻仅允许一个线程访问)就叫做临界资源临界区:每个线程内部,访问临界资源的代码,就叫做临界区互斥:任何时刻,互斥保证有且只有一…

一、背景概念

  • 临界资源:多线程执行流共享的资源(同一时刻仅允许一个线程访问)就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

 Pthread.hpp

namespace MyThread
{template <typename T>using func_t = function<void()>;static int number = 1;enum TSTATUS{NEW,RUNNING,STOP};template <typename T>class Thread{private:// 成员方法! void* Routinue(Thread* this,void* args)static void *Routinue(void *args){Thread<T> *t = (Thread<T> *)args;t->_status = TSTATUS::RUNNING;t->_func();return nullptr;}void EnableDetach(){_joinable = false;}public:Thread(func_t<T> func) : _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread-" + to_string(number++);_pid = getpid();}// 创建bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routinue, this);if (n != 0)return false;return true;}return false;}// 停止bool Stop(){if (_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;return true;}return false;}// 等待bool Join(){if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}// 分离void Detach(){EnableDetach();pthread_detach(_tid);}bool IsJoinable() { return _joinable; }string Name() { return _name; }~Thread(){}private:string _name;pthread_t _tid;pid_t _pid;bool _joinable; // 是否分离,默认不是func_t<T> _func;TSTATUS _status;};}

Main.cc 

#define NUM 4
int ticketnum = 10000; // 共享资源
void Ticket()
{while (true){if(ticketnum > 0){usleep(1000);//抢票printf("get a new ticket,id: %d\n",ticketnum--);}else{break;}}}
int main()
{// 1. 构建线程对象,MyThread: vector<MyThread::Thread> threads;for(int i = 0; i < NUM; i++){threads.emplace_back(Ticket);}// 2. 开始线程for(auto &thread : threads){thread.Start();}// 3. 等待线程for(auto &thread : threads){thread.Join();}return 0;
}
get a new ticket,id: 3
get a new ticket,id: 2
get a new ticket,id: 1
get a new ticket,id: 0
get a new ticket,id: -1

最后的结果:我们执行这个程序后发现ticketnum减到了负数,为什么?

    假设初始 ticketnum 为 1

    1. 线程 A 检查 ticketnum:线程 A 执行 if(ticketnum > 0),此时 ticketnum 为 1,条件成立,进入 if 语句块。
    2. 线程 A 休眠:线程 A 执行 usleep(1000),进入休眠状态。
    3. 线程 B 检查 ticketnum:在这个时候,线程 B 也执行 if(ticketnum > 0),由于线程 A 还没有修改 ticketnum 的值,所以 ticketnum 仍然为 1,条件成立,线程 B 也进入 if 语句块。
    4. 线程 B 继续执行:线程 B 执行完 usleep(1000) 后,执行 ticketnum--,此时 ticketnum 变为 0。
    5. 线程 A 继续执行:线程 A 休眠结束后,接着执行 ticketnum--,由于之前线程 B 已经将 ticketnum 减为 0,所以此时 ticketnum 变为 -1,并且打印出 get a new ticket,id: -1

    ticketnum --这个语句不是原子的

    ticketnum --至少需要三条汇编指令,1.内存数据搬入CPU寄存器 2.CPU内部-- 3.写回内存 

    A、B线程分别要对这个全局变量 ticket 进行 --,A线程先被调度,把1000从内存拿到CPU进行--此时CPU寄存器中的值就是 999, 但由于时间片的问题,A线程被切换,此时就要保存上下文,并该执行流的 task_struct 重新在运行队列排队,但是此时 ticket 在内存中的值仍就为1000,A线程被挂起,B线程开始被调度,B线程很顺利,在时间片期间,进行了多次--操作,此时内存中的 ticket 值以及成为了499,B线程时间片到达,B线程被挂起,A线程重新被调度,第一步就是恢复上下文,然后继续在上次被挂起的地方调度,也就是进行将999重新写回内存。 好,B线程废了九牛二虎之力将 ticket 减到了 499,你A线程一上来,又将 ticket 搞成了999,此时就导致了数据紊乱的问题。

    解决方案???

     二、互斥量Mutex

    要解决以上问题,需要做到三点:

    • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
    • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
    • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

    要做到这三点,本质上就是需要一把锁。Linux 上提供的这把锁叫互斥量。

    NAMEpthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutexSYNOPSIS#include <pthread.h>int pthread_mutex_destroy(pthread_mutex_t *mutex);//⽅法2,动态分配:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);//mutex:要初始化的互斥量//⽅法1,静态分配pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//使⽤ PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
        //互斥量加锁和解锁int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);//pthread_mutex_t是提供的一个定义锁的类型,可以用它来定义一把锁

    2.1 锁是如何实现的?

    经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题。为了实现互斥锁操作,大多数体系结构都提供了 swap 或 exchange 指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。现在我们把 lock 和 unlock 的伪代码改一下。 

    锁已经初始化了,假如有三个进程分别叫做A1,A2,A3 ,同时抢一把锁。

    A1抢到了进行xchgb,把内存中锁初始化的数据1与寄存器中的0进行了交换,之后就进入临界资源了。其他线程没抢到,由于内存中的1被A1抢走了在内存中留下的是A1在寄存器初始化的数据0,所以其他线程xchgb的一直是0只能挂起等待。无法进入临界区。当A1解锁后把1归还到内存中,线程才能重新竞争锁。

    2.2 锁的封装 

    Mutex.hpp  

    namespace LockModule
    {class Mutex{public:Mutex(const Mutex &) = delete;const Mutex& operator =(const Mutex&) = delete;Mutex(){int n = ::pthread_mutex_init(&_lock,nullptr);}~Mutex(){int n = ::pthread_mutex_destroy(&_lock);}void Lock(){int n = ::pthread_mutex_lock(&_lock);}void UnLock(){int n = ::pthread_mutex_unlock(&_lock);}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.UnLock();}private:Mutex &_mtx;};
    }

    Main.cc 

    void *route(void *arg)
    {char *id = (char *)arg;while (1){{LockModule::LockGuard loadguard(mtx); //临时对象 RAIIif (ticket > 0){usleep(1000);ticket--;printf("I am %s ,get a new ticket,id: %d\n",id ,ticket);}else{break;}}}return nullptr;
    }int main(void)
    {pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void *)"thread 1");pthread_create(&t2, NULL, route, (void *)"thread 2");pthread_create(&t3, NULL, route, (void *)"thread 3");pthread_create(&t4, NULL, route, (void *)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
    }

    此时就不会出现负数情况了。

    三、同步

    1. 互斥锁与同步的关系

    • 同步是指协调多个线程的执行顺序,确保它们在访问共享资源时遵循特定规则(如 “先读后写”“缓冲区非空才能取数据”)。同步机制包括互斥锁、条件变量、信号量等。
    • 互斥锁是同步的一种基本实现,它通过强制同一时刻只有一个线程能访问资源来保证互斥性。但互斥锁本身只能解决 “同时访问” 的问题,无法处理更复杂的协作需求(如生产者 - 消费者模型中的 “缓冲区空 / 满” 条件)

    我们前边提到了,线程可以访问同一份资源,可能会出现数据异常的问题,所以我们引入了互斥的概念,通过加锁来使同一时间只有一个线程访问临界资源,当加锁之后,可能又会因为某一线程的竞争力过强,一直处于加锁和销毁锁的状态,导致其他线程不能去访问临界资源而导致饥饿问题,所以我们就要引入线程同步的概念,当一个线程在销毁锁之后,不能立马去申请锁,而是要到队列的末尾排序,进入条件变量的等待队列,不会造成其他的线程长时间不能访问临界资源的情况。 

    3.1 条件变量 

    条件变量是一种同步机制,用于在多个线程之间进行通信。它允许一个线程等待另一个线程满足特定的条件,然后再继续执行。条件变量通常与互斥锁一起使用,以确保线程安全。条件变量提供了一种高效的方式来实现线程之间的同步和通信。        

    当临界资源加锁之后,我们不容易知道临界资源的状态,这时我们可以引入临界变量来获得临界资源的状态,下边我来举一个生活中的例子:

    例如,我们要去书店买一本《剑指offer》,但是去了书店之后,售货员说书卖完了,但是过两天可能会到货,现在我们回家之后,只能等待,那么我们怎么知道书什么时候才有呢?这是有两种方式,第一种方式就是我们没事就去书店一趟,问一下售货员书来了吗,或者是留下售货员的电话,当书来了之后,让售货员给我们打电话,这种打电话的方式我们就可以简单的认为就是使用条件变量的方式来获得临界资源的状态。

    1. 条件变量函数初始化

    //与互斥锁的初始化类似:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
    attr);
    参数:
    cond:要初始化的条件变量
    attr:条件变量的相关属性,默认给NULL

    2.条件变量函数销毁

    int pthread_cond_destroy(pthread_cond_t *cond)

    3.条件变量函数等待条件满足

    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
    参数:
    cond:要在这个条件变量上等待
    mutex:锁

    4.唤醒等待
    唤醒等待一批线程

    int pthread_cond_broadcast(pthread_cond_t *cond)

    唤醒等待一个线程

    int pthread_cond_signal(pthread_cond_t *cond).

    一个小例子:一个线程可以通过条件变量来控制其他的线程。 

    // 锁的初始化
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    // 条件变量
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    void *worker(void *argc)
    {string name = (const char*)argc;while (true){pthread_mutex_lock(&mutex);//没有对于资源是否就绪的判定pthread_cond_wait(&cond,&mutex); // mutex???printf("%s is work\n",name.c_str());pthread_mutex_unlock(&mutex);}}
    int main()
    {pthread_t tid1,tid2,tid3;pthread_create(&tid1,nullptr,worker,(void*)"thread-1-worker");pthread_create(&tid2,nullptr,worker,(void*)"thread-2-worker");pthread_create(&tid3,nullptr,worker,(void*)"thread-3-worker");sleep(3);printf("MAIN thread boss ctrl begin.....\n");while (true){printf("MAIN thread boss look\n");pthread_cond_signal(&cond); //线程在进入等待队列的时候会把锁释放掉// pthread_cond_broadcast(&cond);sleep(1);}pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
    }

    为什么条件变量要在锁的里面呢?(在代码中mutex ??)我们在生产者消费者模型中提及

    MAIN thread boss ctrl begin.....
    MAIN thread boss look
    thread-1-worker is work
    MAIN thread boss look
    thread-2-worker is work
    MAIN thread boss look
    thread-3-worker is work
    MAIN thread boss look
    thread-1-worker is work
    MAIN thread boss look
    thread-2-worker is work
    MAIN thread boss look
    thread-3-worker is work

    3.2 生产者消费者模型

    在日常生活中,我们去超市买东西,我们就是消费者,生产者是供货商

    由于有了超市,可以让生产和消费的行为,进行一定程度解耦,供货商生产商品和消费者是没有关系的,消费者如何对商品做处理供货商关心吗?根本不关心,由于解耦就可以很好的支持并发。

    那么我们把上述抽象出来,超市是一个特定结构的内存空间,而生产者和消费者可以抽象成2个线程,商品是数据,因此生产消费者模型就是2个执行流在做通信。由于超市是供货商和消费者都要访问的,那么这个超市是一种共享资源,多个线程访问共享资源,会有并发问题,并发问题的本质就是在研究角色和角色之间的关系,如果想清楚了生产者、消费者之间的关系,该怎么加锁就很清楚了。

    生产者VS生产者:互斥

    消费者VS消费者:互斥

    生产者VS消费者:互斥、同步

    生产者只有先生产了数据,消费者才能拿数据,因此是有一个先后顺序的,这种先后顺序就是一种同步关系。同一时间内只有一个线程(生产者或消费者)能够访问该资源

    代码实现一下

    BlockQueue.hpp 

    namespace BlockQueueModule
    {template<typename T>class BlockQueue{private:bool IsFull(){return _q.size() == _cap;}bool IsEmpty(){return _q.empty();}public:BlockQueue(int cap = gcap) : _cap(cap), _cwait_num(0), _pwait_num(0){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_productor_cond,nullptr);pthread_cond_init(&_consumer_cond,nullptr);}void Equeue(const T &in){pthread_mutex_lock(&_mutex);// 你想放数据,就能放吗??生产数据是有条件的// 我们如何判断是否需要等待,是否为满。判断属于访问临界区,所以等待必定在临界区内等待// 结论1:在临界区中等待是必然的(目前)while(IsFull()) // 5. 对条件进行伪唤醒,为了避免伪唤醒因此用了while{cout << "生产者进入等待....." << endl;_pwait_num++;// 2.等时,释放_mutexpthread_cond_wait(&_productor_cond,&_mutex); // wait的时候,必定是持有锁的!但是持有锁有问题,因为这把锁还要被消费者申请,所以要释放锁// 3.返回,线程被唤醒&&重新申请并持有锁(它会在临界区内醒来!!)_pwait_num--;cout << "生产唤醒....." << endl;}// 4. if(IsFull())不满足 || 线程被唤醒_q.push(in); // 生产,保证目前不为满// 肯定有数据if(_cwait_num){cout << "叫醒消费者" << _cwait_num <<endl;// 通知pthread_cond_signal(&_consumer_cond);}pthread_mutex_unlock(&_mutex);}void Pop(T *out){pthread_mutex_lock(&_mutex);while(IsEmpty()){cout << "消费进入等待....." << endl;_cwait_num++;pthread_cond_wait(&_consumer_cond,&_mutex);_cwait_num--;cout << "消费唤醒....." << endl;}// if(IsEmpty())不满足||线程被唤醒*out = _q.front();_q.pop();//肯定有空间if(_pwait_num){cout << "叫醒生产者" << _pwait_num <<endl;// 通知pthread_cond_signal(&_productor_cond); }pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_productor_cond);pthread_cond_destroy(&_consumer_cond);}private:queue<T> _q;int _cap;                        // 最大容量pthread_mutex_t _mutex;         // 互斥pthread_cond_t _productor_cond; // 生产者条件变量pthread_cond_t _consumer_cond;  // 消费者条件变量int _cwait_num;int _pwait_num;};
    }

    Main.cc 

    void *Consumer(void *argc)
    {BlockQueue<int> *bq = (BlockQueue<int>*)argc;while (true){int data;// 1. 从bq拿到数据bq->Pop(&data);// 2. 做处理printf("Consumer 消费的是: %d数据\n",data);}}
    void *Product(void *argc)
    {BlockQueue<int> *bq = (BlockQueue<int>*)argc;int data = 0;while (true){// 1. 从外部获取数据// 2. 生产到bq中sleep(2);bq->Equeue(data);printf("producter 生产了一个数据:%d\n",data);data++;}}int main()
    {BlockQueue<int> *bq = new BlockQueue<int>(10); //共享资源 -> 临界资源// 单生产,单消费pthread_t c1,c2,p1,p2,p3;pthread_create(&c1,nullptr,Consumer,bq);pthread_create(&c2,nullptr,Consumer,bq);pthread_create(&p1,nullptr,Product,bq);pthread_create(&p2,nullptr,Product,bq);pthread_create(&p3,nullptr,Product,bq);pthread_join(c1,nullptr);pthread_join(c2,nullptr);pthread_join(p1,nullptr);pthread_join(p2,nullptr);pthread_join(p3,nullptr);delete bq;return 0;
    }

     理解完上面的代码我们聊一下(上面提及的为什么条件变量要在锁的里面呢?)

    当我们生产者或者消费者判断是否等待的时候我们要先进入临界资源进行判断是否为满,是否为空,那我们是不是就要进行对临界资源的保护?所以我们等待的时候要在临界区。接下来我以生产者为例,既然临界资源为满,我们生产者在等待,等待的时候要释放锁,以便于其他消费者拿着锁进行消费。所以条件变量要在锁里面。

     伪唤醒

    但是在面临多生产多消费的情况,我们会出现伪唤醒的情况。

    我们看到,我们在判断临界资源状态的时候用了while循环,注释里写是为了避免伪唤醒因此用了while,那么我们怎么理解伪唤醒呢?假设现在队列为满,生产者没办法消费了,因此来的生产者线程都要去条件变量的队列中等待。但此时消费者是可以消费的,当消费者消费了一个之后会唤醒生产者继续生产,如果此时生产者不是使用的pthread_cond_signal唤醒的一个线程,而是使用pthread_cond_broadcast唤醒了生产者对列中的所有线程,唤醒之后需要重新持有锁,因此这被唤醒的多个线程就会对锁展开竞争(pthread_cond_wait(&_productor_cond,&_mutex);),最终肯定只有一个线程能拥有锁往后执行,于是拥有锁的那个是继续往后执行push数据,push数据之后队列为满了,之后唤醒消费者让消费者消费,但是,这个锁的竞争者不仅仅是消费者还有刚刚那些唤醒的生产者线程,如果锁被刚刚那些唤醒的生产者线程把锁抢到了呢?那些被唤醒的生产者线程不在条件变量下等了,而是在这把锁等,因为刚刚生产者线程把锁释放了,此时一个生产者线程立刻持有了锁,继续往后push数据,但此时队列的容量是满的呀,所以此时就产生了一种伪唤醒或者误唤醒的情况。怎么防止伪唤醒呢?while判断,如果一个线程被唤醒了,你先别着急往后走,先在去判定一下,如果的空间没满你再去生产,如果满了重新进入休眠状态。

    补充: 

     生产消费模型为什么高效? 

    生产过程要加锁,消费的时候也要加锁,在访问仓库的时候是串行的,哪里体现高效了呢? 如果一个线程正在仓库生产数据,其他线程的确不能生产了,但它可不可以获取数据?获取数据也是要花时间的哦。生产者正在互斥式的访问仓库生产数据,消费者也不消费,消费者有没有可能在加工处理数据呢?因此如果我们引入生产者获取数据消费者加工处理数据,那么此时不就是并发执行的吗?只不过一些线程访问临界区代码,一些线程访问非临界区代码,这里线程不就高效并发起来了吗?因此,生产消费者才是高效的。

    3.3 为什么 pthread_cond_wait需要互斥量?

    条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。

    条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

    按照上⾯的说法,我们设计出如下的代码:先上锁,发现条件不满⾜,解锁,然后等待在条件变
    量上不就行了,如下代码: 

    // 错误的设计
    pthread_mutex_lock(&mutex);
    while (condition_is_false) {pthread_mutex_unlock(&mutex);// 解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);
    }
    pthread_mutex_unlock(&mutex);

    为什么错误?? 

    图中线程 A 先获取锁,进行条件判断后解锁,接着进入等待状态;线程 B 在后续获取锁。这种场景下,若线程 A 解锁后、进入等待前,线程 B 获取锁并修改条件(使条件满足)且发送信号,由于线程 A 此时还未进入等待状态(未调用 pthread_cond_wait 真正挂起),该信号会被错过,导致线程 A 后续永久阻塞在 pthread_cond_wait 

    3.4 信号量

    在上述生产者消费者模型中,这个共享资源q是被当做一个整体来使用的,因此我们需要加锁来确保安全性,我们可不可以把这个资源q切成几个不同的区域。现在有一个数组,数组中有300个元素,现在有3个线程,我让这3个线程分别只能访问前100个、中间100个、后100个元素,这样虽然数组是个全局数组,但我们访问的是这个数组的不同区域,因此这3个线程可以并发访问这个数组的不同区域吗?当然可以呀,但是要来第4个线程呢?这个数组我只划分了3份但过来了4个线程,那我怎么确保数据安全呢?我只能让你3个线程来访问,哪3个呢?不重要,只要是3个都行,因此这个数组的资源把它当做整体来用直接加锁,只允许一个线程进来。但我们如果把它分成几份,那最多就允许几个线程进来,为了更好的保护临界资源就需要引入信号量进来,你说分成3份就分成3份吗?最多允许多少个线程进来,就由信号量来决定。

    3.4.1 什么是信号量 

    信号量本质是一把计数器,描述临界资源中资源数目的多少。

    比如你开了一家停车场,有 5 个停车位(资源)。

    • 信号量的初始值:5(表示有 5 个可用车位)。
    • 当车辆进入(线程 / 进程申请资源):如果有空位,允许进入;否则在门口等待。
    • 当车辆离开(线程 / 进程释放资源):腾出一个空位,唤醒等待的车辆。

    (1)P 操作(Wait)

    目标:申请一个资源(如停车位)。
    步骤

    1. 检查信号量值:若信号量 > 0 → 可用资源足够。
    2. 原子减 1:占用一个资源(信号量 = 信号量 - 1)。
    3. 若信号量 ≤ 0:资源不足 → 将当前线程放入阻塞队列(类似停车场入口的等待队伍)。

    (2)V 操作(Signal)

    目标:释放一个资源(如腾出停车位)。
    步骤

    1. 原子加 1:归还资源(信号量 = 信号量 + 1)。
    2. 检查阻塞队列:若有线程在等待 → 唤醒一个线程(类似停车场管理员叫号)。

     3.4.2 信号量的使用

    (1)定义信号量

    sem_t xxx; // sem_t是一个整形,可以定义信号量变量。

    (2)初始化信号量

    #include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
    // 参数: pshared:0表示线程间共享,非零表示进程间共享value:信号量初始值

    (3)销毁信号量 

    int sem_destroy(sem_t *sem);

     (4)等待信号量

    //功能:等待信号量,会将信号量的值减1int sem_wait(sem_t *sem);  // P操作

    (5)发布信号量

     //功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。int sem_post(sem_t *sem); //V操作

    3.4.3 基于环形队列的生产消费模型 

    生产者和消费者刚开始都在同一个位置,但是没有关系,刚开始只有谁能先往后走呢?只有生产者能往后走,因为消费者没有数据给它消费呀。生产者最关心的是什么资源?是当前有多少个空间给我放数据,消费者关心什么资源呢?还有多少数据可以让我取,因此我们可以定义2个信号量,SpaceSem表示还有多少剩余空间,DataSem表示还有多少剩余数据。当生产者P操作生产了一个之后,是不是数据就多1个呀,此时我们不要V自己,要V的是数据信号量DataSem,让数据多一个,此时消费者就可以消费了,同理,消费者在P操作过后,空间就多出来一个,因此要V空间信号量SpaceSem 。如果生产者一直生产,消费者就是不消费,然后把空间使用完了此时生产者和消费者又在同一个位置哦,请问,如果满了会发生什么,满了就只能等消费者消费!因为生产者的空间已经使用完了,此时P操作就会把它挂起,需要等消费者消费让空间多出来。

    因此只有2种情况才会指向同一个位置,空或者满,如果是空,必然生产者现在。如果是满,必然消费者先走!

    当不空和不满的时候,我们一定指向不同的位置,我们可以同时访问!

    在多生产和多消费的过程中我们要申请几把锁?

    我们可以申请一把锁,但是当不空和不满的时候,我们生产者和消费者是并发执行的,所以这里我们使用的是两把锁。

     先封装一下信号量Sem.hpp

    namespace SemModule
    {int defalutsemval = 1;class Sem{public:Sem(int value = defalutsemval):_init_value(value){int n = ::sem_init(&_sem,0,_init_value);}void P(){int n = ::sem_wait(&_sem);}void V(){int n = ::sem_post(&_sem);}~Sem(){int n = ::sem_destroy(&_sem);}private:sem_t _sem;int _init_value;};
    }

    RingBuffer.hpp

    namespace RingBufferModule
    {using namespace SemModule;using namespace std;template <class T>class RingBuffer{public:RingBuffer(int cap): _ring(cap), _cap(cap), _p_step(0), _c_step(0),_datasem(0),_spacesem(_cap){pthread_mutex_init(&_p_lock,nullptr);pthread_mutex_init(&_c_lock,nullptr);}void Equeue(const T &in){// 生产者// pthread_mutex_lock(&_p_lock);//先锁还是先信号量??? 先信号量效率更高为什么?就类似于并发申请互斥量(买票),然后在申请锁(检票)_spacesem.P();pthread_mutex_lock(&_p_lock);_ring[_p_step] = in; // 生产完毕_p_step++;_p_step %= _cap; // 维持唤醒特性pthread_mutex_unlock(&_p_lock);_datasem.V();}void Pop(T *out){// 消费者_datasem.P();pthread_mutex_lock(&_c_lock);*out = _ring[_c_step];_c_step++;_c_step %= _cap;pthread_mutex_unlock(&_c_lock);_spacesem.V();}~RingBuffer(){pthread_mutex_destroy(&_p_lock);pthread_mutex_destroy(&_c_lock);}private:vector<T> _ring; // 环,临界资源int _cap;        // 总容量int _p_step;     // 生产者位置int _c_step;     // 消费者位置Sem _datasem;  // 数据信号量Sem _spacesem; // 空间信号量pthread_mutex_t _p_lock; //生产者的锁pthread_mutex_t _c_lock; //消费者的锁};
    }

    Main.cc 

    void *Consumer(void *argc)
    {RingBuffer<int> *ring_buffer = (RingBuffer<int>*)argc;while (true){// sleep(1);int data;ring_buffer->Pop(&data);cout << "消费了一个数据:" << data << endl;}}
    void *Product(void *argc)
    {RingBuffer<int> *ring_buffer = (RingBuffer<int>*)argc;int data = 0;while (true){// 1.获取数据sleep(1);// 2.生产数据ring_buffer->Equeue(data);cout << "生产了一个数据:" << data << endl; data++;}}
    int main()
    {RingBuffer<int> *ring_buffer = new RingBuffer<int>(5);pthread_t c1,p1;pthread_create(&c1,nullptr,Consumer,ring_buffer);pthread_create(&p1,nullptr,Product,ring_buffer);pthread_join(c1,nullptr);pthread_join(p1,nullptr);delete ring_buffer;return 0;
    }

    参考的文章

       线程的互斥与同步_线程同步和互斥-CSDN博客

     【Linux】线程详解之线程互斥与同步_linux进程资源互斥-CSDN博客

    http://www.dtcms.com/wzjs/543312.html

    相关文章:

  1. php快速建站系统做国外服务器网站
  2. 营销型网站设计价格钦州的网站建设
  3. 苏州资讯网站建设上海市建设协会考试网站
  4. 北京房产网站建设怎样做 云知梦 网站
  5. 怎样做QQ网站呢建站 网站程序
  6. 免费建站软件排行榜电子商务网站建设的过程
  7. 织梦网站手机版端设置2024年楼市大局已定
  8. 漂亮的博客网站模板广州装饰公司
  9. 自己做自媒体在哪个网站比较好网络推广运营优化
  10. 中国建设人才信息网站网站开发asp软件有哪些
  11. 平台制作网站公司哪家好家装要去哪个公司装修
  12. 手机网站被禁止访问怎么打开备案网站内容说明
  13. 有域名了怎么做网站自己做视频网站
  14. 网站的链接结构怎么做网站建设考级
  15. 网站浮动窗口代码怎样使用二维码做网站
  16. 如何做国际网站wordpress成品站源码
  17. 牛商网做网站多少钱台安人才网
  18. 广东外贸网站推广设计素材网站模板
  19. 做外汇关注的网站中国林业建设协会网站
  20. 如何建立自己的网站网页请求流程
  21. 怎么做网站海报网页游戏怎么开发
  22. 网站推广预期达到的目标视频剪辑在哪里学
  23. 响应式网站公司所有做网站公司
  24. 做废品回收在什么网站推广网站程序制作教程
  25. 100m的光纤可以做网站吗网站建设公司那家好
  26. 北京做网站好荆州公司网站建设
  27. 做网站用cms好吗效能建设网站
  28. wordpress的目录结构如何做好网站seo优化
  29. 网站建设 用英文怎么说网站制作公司业务员
  30. 要想让别人网站卖我的东西怎么做东莞广告公司招聘