线程的互斥与同步

目录

Linux中的线程与进程

一、线程栈

1、线程自己的栈空间

2、线程局部存储

二、线程分离

三、线程互斥

多线程并发抢票:

对一个全局变量多线程并发--

例子:

tips:寄存器的内容!=寄存器

数据不一致的原因:

为什么票数<=0时还能抢票呢?

四、互斥锁

1、init/destroy

2、lock/trylock/unlock

场景1:

问题1:

锁的竞争性与饥饿问题:

饥饿问题:

解决方案--同步:

锁的申请和释放是原子性的:

实现原理:

例子1:A-lock

​编辑

​编辑

例子2:B-lock

unlock

总结:

五、锁的应用

简单封装

六、线程安全与可重入

七、死锁

2个线程2把锁的死锁场景

死锁四个必要条件:

解决死锁问题:

编码上避免死锁的原则

八、线程同步

同步概念

简单实现同步(条件变量)

九、生产消费者模型

BlockQueue

demo-1

demo-2

Task.hpp模拟任务

RingQueue

demo-1

demo-2

Linux中的线程与进程

一、线程栈

1、线程自己的栈空间

线程库会被加载到进程地址空间中(共享区),tid为线程对象的起始地址。

多线程情况下测试局部变量test_i

#define NUM 5
struct threadData
{
    string threadname;
};
string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"0x%x",tid);
    return buffer;
}
void* threadRoutine(void* args)
{
    threadData* td = static_cast<threadData*>(args);
    int cnt=10;
    int test_i=0;
    while(cnt--)
    {
        cout<<"pid:"<<getpid()<<"  tid:"<<toHex(pthread_self())
        <<"  test_i:"<<test_i++
        <<" &test_i:"<<&test_i
        <<"  threadName:"<<td->threadname<<endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}

void InitData(threadData* td,int number)//面向过程
{
  td->threadname = "thread-" + to_string(number);
}
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;++i)
    {
        pthread_t tid;
        threadData* td = new threadData;
        InitData(td,i);
        pthread_create(&tid,nullptr,threadRoutine,td);//create线程的时候写入tid
        tids.push_back(tid);
        sleep(1);
    }

    for(int i=0;i<tids.size();++i)
    {
        pthread_join(tids[i],nullptr);
    }

    return 0;
}

        

每个线程的test_i都是独立的,有自己的地址,是在线程各自的栈空间上开辟的。

堆空间是共享的,每个线程分配一块。

int *p = nullptr;定义一个全局的p变量

主线程中可以获取子线程的栈区上的局部变量。

也就是说,线程之间虽然有独立的栈区,但线程之间也是可以做到互相访问的。(在地址空间中

但实际使用时,规定不能这样使用。

2、线程局部存储

int g_val1=0;

__thread编译选项,运用线程局部存储原理,在共享区上创建一个私有全局变量

只能创建内置类型

应用:可以保存一些需要系统调用的值(获取一些基本属性),提高效率。

__thread int g_val2=0;

二、线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
pthread_detach(pthread_self());
joinable 和分离是冲突的,一个线程不能既是 joinable 又是分离的。
主线程分离
  for (auto i : tids)
    {
        pthread_detach(i);//主线程分离
    }
   
    for (int i = 0; i < tids.size(); i++)
    {
        int n = pthread_join(tids[i], nullptr);
        printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
    }
其它线程自己分离
注:进程分离后,必须保证进程最后退出。否则分离后,进程join时不再阻塞等待,进程结束,进程退出,所有线程都会退出,该做的任务就没有完成。
主线程调用pthread_exit只是退出主线程,并不会导致进程的退出
是否被分离,是看线程tcb中自身的属性是joinable还是分离的(0还是1)

三、线程互斥

进程线程间的互斥相关背景概念
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥量mutex
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
多个线程并发的操作共享变量,会带来一些问题。

多线程并发抢票:

// -----------多线程并发抢票,互斥
#define NUM 4

int tickets = 1000;
class threadData
{
public:
    threadData(int number)
    {
        threadName = "thread-" + to_string(number);
    }
public:
    string threadName;
};
string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}
void *getTicket(void *args)
{
    threadData* td = static_cast<threadData*>(args);
    while(tickets>0)
    {
        usleep(10000);
        --tickets;
        cout<<"threadname:"<<td->threadName<<" tid:"
<<toHex(pthread_self())<<" 剩余票数:"<<tickets<<endl;
    }
    cout<<"quit..."<<td->threadName<<endl;

    return nullptr;
} 
int main()
{
    vector<pthread_t> tids;
    vector<threadData*> thread_Datas;
    for (int i = 1; i <= NUM; ++i)
    {
        pthread_t tid;
        threadData* td = new threadData(i);
        thread_Datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, thread_Datas[i-1]);
        tids.push_back(tid);
    }

    for(auto tid:tids)
    {
        pthread_join(tid,nullptr);
    }
    for(auto td:thread_Datas)
    {
        delete td;
    }
    return 0;
}

对一个全局变量多线程并发--

tickets--的操作不是原子性的,而是分为三个步骤:

  1. 将共享变量tickets从内存加载到寄存器中
  2. 更新寄存器里面的值 执行-1操作
  3. 将新值从寄存器写回共享变量tickets的内存地址

例子:

假设现在有2个线程thread-1 和 thread-2进行抢票工作,分为上面的1,2,3步。

对于thread-1:执行第一步,从内存中读取到1000,1步完成。此时时间片到了,切换为thread-2

对于thread-2:时间片剩余较多,假设可以完整完成100次抢票工作。最后一次完成3步,此时eax中保存的值是900,最终将其写回内存中。然后切换回thread-1.

对于thread-1:继续第2步,先恢复上下文数据,将1000写到eax中,计算后为999,再进行第三步写回内存,此时内存中的值就变为999了,也就是多了100张票。

即对于thread2来说,tickets值前后不一致,即数据不一致问题。

tips:寄存器的内容!=寄存器

保存上下文到线程的对象内部,每次轮转到时恢复到CPU内的寄存器中

也就是thread-1认为自己一直在正确地--,实际上保存在上下文的那一份,拷贝回内存时,导致了最终的数据不一致问题。

数据不一致的原因:

tickets--的操作不是原子性的,即允许多个执行流同时进入,会互相干扰。

为避免该问题,则需要加锁操作。

为什么票数<=0时还能抢票呢?

判断tickets时,成立进入。

但设置了usleep,为了让多线程都停留在判断进入,但没有--操作,就被切换走了。

此时就会出现tickets为1,但有>1个线程判断成立。

后续的--操作就不判断tickets的值是否>0了

每次--都需要重新读取tickets的值,在这之前tickets可能已经被其它线程修改了。

四、互斥锁

1、init/destroy

pthread_mutex是库提供的一种数据类型

全局的mutex不用手动init和destroy

在main函数中创建并init一个lock。

在threadData中加入一个锁的指针,多线程对于一个临界区,共用一把锁。

2、lock/trylock/unlock

多线程共享的资源是临界资源,访问临界资源的代码叫临界区。

加锁本质:是用时间换安全。

加锁表现:原来多线程并发执行,对于加锁的临界区的代码变为串行执行。(并发度下降)

加锁原则:临界区的代码越少越好。

场景1:

lock和unlock之间的代码就是临界区。

问题1:

把lock和unlock放在while(1)外部会怎么样?

一直是一个线程在抢票(执行while(1)),与逻辑不符。

问题2:

lock失败则会阻塞等待,直到申请成功。

问题3:

tickets为0时,break跳出,不会unlock

在if和else中都要加unlock

锁的竞争性与饥饿问题:

上述代码加锁后,不会出现负数情况,但为什么还是一个线程在抢票?

这是由于多个线程对于锁的竞争性不同导致的。

对于lock的线程,unlock后可以直接继续lock,中间间隔很短,lock的概率大

只要lock成功,即使轮转到别的线程,其它线程也只能阻塞等待。

对于其它线程,线程的切换需要的时间很长,lock的概率就很小。

在抢票完成后usleep,此时持有锁的进程不会立刻下一次lock,而是和其它线程一样进行时间片轮转,多线程直接lock的概率差距就变小了,即对锁的竞争性就差不多了。

饥饿问题:

解决方案--同步:

锁的申请和释放是原子性的:

联系信号量,PV操作的设计也是原子性不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成)

锁是为了保护临界资源的,各线程申请一把锁,锁本身也是临界资源

 线程切换时,是持有锁一起切换走的,这期间其它线程不能进入临界区访问临界资源。该持有锁的线程访问临界区的过程,对其它线程来说是原子的。

实现原理:

为了实现互斥锁的操作,大多数体系结构都提供了swapexchange指令,该指令的作用就是把寄存器和内存单元的数据相交换(一条汇编指令完成,即原子性的


以加锁示例,这是由多条汇编语句执行的,上述%al是寄存器mutex就是内存中的一个变量。每个线程申请锁时都要执行上述语句,执行步骤如下:

  • movb $0%al先将al寄存器中的值清0。该动作可以被多个线程同时执行,因为每个线程都有自己的一组寄存器(上下文信息),执行该动作本质上是将自己的al寄存器清0。注意:凡是在寄存器中的数据,全部都是线程的内部上下文!多个线程看起来同时在访问寄存器,但是互不影响。
  • xchgb %almutex然后用此一条指令交换al寄存器和内存中mutex的值,xchgb是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换
  • 最后判断al寄存器中的值是否大于0。若大于0则申请锁成功,此时就可以进入临界区访问对应的临界资源;否则申请锁失败需要被挂起等待,直到锁被释放后再次竞争申请锁。

例子1:A-lock

现在线程A要开始加锁,执行上述语句。首先(movb $0%al),线程A把0读进al寄存器(清0寄存器)

然后执行第二条语句(xchgb %almutex)将al寄存器中的值与内存中mutex的值进行交换。

  • 当线程A争议执行第三条语句if判断时,发生了线程切换(切至线程B),但是线程A要把自己的上下文(1)带走。线程B也要执行加锁动作,同样是第一条语句把0加载到寄存器,清0寄存器。

  • 随后线程B执行第二条语句交换动作,可是mutex的数据先前已经被线程A交换至寄存器,然后保存到线程A的上下文了,现在的mutex为0,而线程B执行交换动作,拿寄存器al的0去换内存中mutex的0。

最终A进行lock成功,B被挂起等待。

例子2:B-lock

线程A在执行第一条语句把寄存器清0后就发生了线程切换(切至线程B),线程A保存上下文数据(0),此时线程B执行第一条语句把0写进寄存器,随后线程B执行第二条语句xchgb交换:

此时线程A执行第三条语句if判断失败,只能被挂起等待,线程A只能把自己的上下文数据保存,重新切换至线程B,也就是说线程B只要不运行,你们其它所有线程都无法申请成功。线程B恢复上下文数据(1)到寄存器,然后执行第三条语句if成功,返回结果。

交换的本质上述xchgb就是申请锁的过程。申请锁是将数据从内存交换到寄存器,本质就是将数据从共享内存变成线程私有。 

  • mutex就是内存里的全局变量,被所有线程共享,但是一旦用一条汇编语句将内存的mutex值交换到寄存器,寄存器内部是哪个线程使用,那么此mutex就是哪个线程的上下文数据,那么就意味着交换成功后,其它任何一个线程都不可能再申请锁成功了,因为mutex已经独属于某线程私有了。
  • 这个mutex = 1就如同令牌一般,哪个线程先交换拿到1,那么哪个线程就能申请锁成功,所以加锁是原子的。

unlock

进行unlock的一般都是是lock成功的那个线程,因此天然具有原子性。

某些情况下,也可以让其它线程进行unlock。

当线程释放锁时,需要执行以下步骤:

  1. 将内存中的mutex置回1。使得下一个申请锁的线程在执行交换指令后能够得到1,形象地说就是“将锁的钥匙放回去”。
  2. 唤醒等待Mutex的线程。唤醒这些因为申请锁失败而被挂起的线程,让它们继续竞争申请锁。

总结:

  • 在申请锁时本质上就是哪一个线程先执行了交换指令,那么该线程就申请锁成功,因为此时该线程的al寄存器中的值就是1了。而交换指令就只是一条汇编指令,一个线程要么执行了交换指令,要么没有执行交换指令,所以申请锁的过程是原子的。
  • 线程释放锁没有将当前线程al寄存器中的值清0,这不会造成影响,因为每次线程在申请锁时都会先将自己al寄存器中的值清0,再执行交换指令。
  • CPU内的寄存器不是被所有的线程共享的,每个线程都有自己的一组寄存器,但内存中的数据是各个线程共享的。申请锁实际就是,把内存中的mutex通过交换指令,原子性的交换到自己的al寄存器中。

五、锁的应用

简单封装

class Mutex//封装Lock和Unlock接口
{
public:
    Mutex(pthread_mutex_t* lock)
    :_lock(lock){}

    ~Mutex(){}

    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }

private:
    pthread_mutex_t* _lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock)
    :_mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

使用一个全局的锁,且创建一个LockGuard设计RAII风格的锁

加一个代码块,让LockGuard完成RAII功能。

六、线程安全与可重入

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数

七、死锁

2个线程2把锁的死锁场景

// int 票数计数器
int tickets = 1000; // 临界资源,可能会因为共同访问,造成数据不一致的问题
pthread_mutex_t Mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t Mutex1=PTHREAD_MUTEX_INITIALIZER;
void *getTickets(void *args)
{
    const char *name = static_cast<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&Mutex);
        sleep(1);
        pthread_mutex_lock(&Mutex1);
        // 临界区
        if (tickets > 0)
        {
            usleep(100);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
        }else
        {
            break;
        }
        pthread_mutex_unlock(&Mutex1);
        pthread_mutex_unlock(&Mutex);
    }
    return nullptr;
}

void *getTickets1(void *args)
{
    const char *name = static_cast<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&Mutex1);
        sleep(1);
        pthread_mutex_lock(&Mutex);
        // 临界区
        if (tickets > 0)
        {
            usleep(100);
            cout << name << " 抢到了票, 票的编号: " << tickets << endl;
            tickets--;
        }else
        {
            break;
        }
        pthread_mutex_unlock(&Mutex);
        pthread_mutex_unlock(&Mutex1);
    }
    return nullptr;
}

死锁四个必要条件:

互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
对于1把锁:连续调用lock函数就会死锁
对于>=2把锁:死锁时一定满足上面4个必要条件

解决死锁问题:

破坏4个必要条件中的一个即可。

1、互斥条件(编码):也就是不再使用锁,一般不会改变。

2、变为请求与不保持条件(接口)

例如A和B两个线程,A申请到了a锁,B申请到了b锁,然后A又要申请b锁,B又要申请a锁。

使用pthread_mutex_trylock接口,A申请b锁时,由于B持有锁,所以申请失败直接返回,if成功执行临界区代码,else失败会把a锁释放,然后再重新申请2把锁。

释放a锁后,B线程就可以正常申请到第二把a锁,使用完后释放a锁和b锁。

3、变为剥夺条件(接口)

线程A会因为没申请到b锁而阻塞,此时如果可以让B线程把b锁释放,A线程就可以申请成功了。

4、循环等待条件(编码)

2个线程需要2把锁,按照顺序申请锁,都是先申请a锁,再申请b锁

如:线程A申请到a锁,线程B申请a锁时会阻塞,此时b锁不在线程B上,线程A可以正常拿到b锁,然后执行完代码后依次释放a锁和b锁。

之后线程B才能申请这两把锁。

编码上避免死锁的原则

破坏死锁的四个必要条件
3~加锁顺序一致
2~避免锁未释放的场景
1~资源一次性分配

八、线程同步

同步概念

同步:在保证数据安全(有锁)的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

如果没有锁,新来的线程都会直接访问临界资源,失败了才会排队,但访问期间就会导致线程不安全。

如果没有饥饿问题,只用互斥即可。

有饥饿问题,则需要同步。

简单实现同步(条件变量)

让阻塞的线程去排队(按一定顺序)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int cnt=0;
void* Cond(void* args)//condition
{
    pthread_detach(pthread_self());
    uint64_t number = (uint64_t)args;
    cout<<"thread-"<<number<<" create success "<<endl;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        cout<<"number:"<<number<<"  cnt:"<<cnt++<<endl;
       // sleep(3);
        pthread_mutex_unlock(&mutex);
    }
    return nullptr;
}

int main()
{
    for(uint64_t i=0;i<5;++i)
    {
        pthread_t tid;
        pthread_create(&tid,nullptr,Cond,(void*)i);//把值作为指针传入,传值拷贝,使i不是共享资源,不要传地址&i
        usleep(1000);   
    }
    sleep(3);
    cout<<"main thread ctrl begin"<<endl;
    while(1)
    {
        sleep(1);
        pthread_cond_signal(&cond);//唤醒cond等待队列中的第一个线程
        cout<<"signal one thread"<<endl;
    }
    return 0;
}

九、生产消费者模型

BlockQueue

template <class T>
class BlockQueue
{
    const static int defaultNum = 20;

private:
    queue<T> q_;
    int maxcap_;
    // 保证生产者和消费者之间是互斥+同步的
    pthread_mutex_t mutex_;// 互斥
    pthread_cond_t c_cond_;// 同步
    pthread_cond_t p_cond_;//分别放在2个不同的条件变量的队列中
    int _high_water_ = maxcap_*2/3;//水位线控制策略
    int _low_water_ = maxcap_/3;
public:
    BlockQueue(int maxcap = defaultNum) : maxcap_(maxcap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&c_cond_, nullptr);
        pthread_cond_init(&p_cond_, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&mutex_);
        // 判断保证可以生产
        if (q_.size() == maxcap_) // 判断时也要访问临界资源,需要放在加锁之后
        {
            pthread_cond_wait(&p_cond_, &mutex_); // 1、调度的时候自动释放锁
        }
        // 1、队列不满 2、满了后被唤醒,唤醒是因为已经有消费了
        q_.push(in);
        if(q_.size()>_high_water_)pthread_cond_signal(&c_cond_);
        pthread_mutex_unlock(&mutex_);
    }
    const T pop()
    {
        pthread_mutex_lock(&mutex_);
        // 判断保证可以消费
        if (q_.size() == 0) 
        {
            pthread_cond_wait(&c_cond_, &mutex_); 
        }
        T out = q_.front();
        q_.pop();
        if(q_.size()<_low_water_)pthread_cond_signal(&p_cond_);
        pthread_mutex_unlock(&mutex_);
        return out;
    }
};

demo-1

生产者和消费者都是单线程,传递int类型

#include"BlockQueue.hpp"

void* Productor(void* args)
{
    BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);
    int data = 0;
    while(1)
    {
        data++;
        bq->push(data);
        cout<<"生产了一个数据 "<<data<<endl;
        sleep(1);
    }

    return nullptr;
}
void* Consumer(void* args)
{
    BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);
    while (1)
    {
        int data = bq->pop();
        cout<<"消费了一个数据 "<<data<<endl;
        //sleep(1);
    }
    

    return nullptr;
}
int main()
{
    BlockQueue<int>* bq = new BlockQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,Consumer,bq);
    pthread_create(&p,nullptr,Productor,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    delete bq;

    return 0;
}

demo-2

生产者和消费者都是多线程,传递Task

#include "task.hpp"
#include"BlockQueue.hpp"
#define NUM_PRODUCTOR 10
#define NUM_CONSUMER 1

void *Consumer(void *args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        // 消费
        Task t = bq->pop();

        // 计算
        // t.run();
        t();

        std::cout << "处理任务: " << t.GetTask() << " 运算结果是: " << t.GetResult() << " thread id: " << pthread_self() << std::endl;
        // t.run();
        // sleep(1);
    }
}

void *Productor(void *args)
{
    int len = opers.size();
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);

    int x = 10;
    int y = 20;
    while (true)
    {
        // 模拟生产者生产数据
        int data1 = rand() % 10 + 1; // [1,10]
        usleep(10);
        int data2 = rand() % 10;
        char op = opers[rand() % len];
        Task t(data1, data2, op);

        // 只有与阻塞队列交互时才需要加锁
        bq->push(t);
        std::cout << "生产了一个任务: " << t.GetTask() << " thread id: " << pthread_self() << std::endl;
        sleep(1);
    }
}
int main()
{
    srand(time(nullptr));
    BlockQueue<Task>* bq = new BlockQueue<Task>();
    pthread_t pdor[NUM_PRODUCTOR];
    pthread_t cmer[NUM_CONSUMER];
    for(int i=0;i<NUM_PRODUCTOR;++i)
    {
        pthread_create(&pdor[i],nullptr,Productor,bq);
        usleep(100);
    }
    for(int i=0;i<NUM_CONSUMER;++i)
    {
        pthread_create(&cmer[i],nullptr,Consumer,bq);
        usleep(100);
    }

    for(int i=0;i<NUM_PRODUCTOR;++i)
        pthread_join(pdor[i],nullptr);
    for(int i=0;i<NUM_CONSUMER;++i)
        pthread_join(cmer[i],nullptr);

}

Task.hpp模拟任务

#pragma once
#include <iostream>
#include <string>

std::string opers="+-*/%";

enum{
    DivZero=1,
    ModZero,
    Unknown
};

class Task
{
public:
    Task(int x=0, int y=0, char op=opers[0]) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0)
    {
    }
    void run()
    {
        switch (oper_)
        {
        case '+':
            result_ = data1_ + data2_;
            break;
        case '-':
            result_ = data1_ - data2_;
            break;
        case '*':
            result_ = data1_ * data2_;
            break;
        case '/':
            {
                if(data2_ == 0) exitcode_ = DivZero;
                else result_ = data1_ / data2_;
            }
            break;
        case '%':
           {
                if(data2_ == 0) exitcode_ = ModZero;
                else result_ = data1_ % data2_;
            }            break;
        default:
            exitcode_ = Unknown;
            break;
        }
    }
    void operator ()()
    {
        run();
    }
    std::string GetResult()
    {
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r += "=";
        r += std::to_string(result_);
        r += "[code: ";
        r += std::to_string(exitcode_);
        r += "]";

        return r;
    }
    std::string GetTask()
    {
        std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r += "=?";
        return r;
    }
    ~Task()
    {
    }

private:
    int data1_;
    int data2_;
    char oper_;

    int result_;
    int exitcode_;
};

RingQueue

#pragma once
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <thread>
#include <stdio.h>
#include <vector>
#include <string.h>
#include <string>
#include <ctime>
#include <vector>
#include <semaphore.h>
using namespace std;
const int DEFAULT_CAPACITY = 5;
template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t &lock)
    {
        pthread_mutex_lock(&lock);
    }
    void Unlock(pthread_mutex_t &lock)
    {
        pthread_mutex_unlock(&lock);
    }

public:
    RingQueue(int cap = DEFAULT_CAPACITY)
        : _cap(cap), _ring_queue(cap), c_step(0), p_step(0)
    {
        sem_init(&c_data, 0, 0);
        sem_init(&p_space, 0, cap);
        pthread_mutex_init(&p_lock, nullptr);
        pthread_mutex_init(&c_lock, nullptr);
    }
    ~RingQueue()
    {
        sem_destroy(&c_data);
        sem_destroy(&p_space);
        pthread_mutex_destroy(&c_lock);
        pthread_mutex_destroy(&p_lock);
    }
    void Push(const T &in)
    {
        P(p_space);
        Lock(p_lock);

        _ring_queue[p_step] = in;
        // 维持环形队列
        p_step++;
        p_step %= _cap;

        Unlock(p_lock);
        V(c_data);
    }
    void Pop(T *out)
    {
        P(c_data);
        Lock(c_lock);

        *out = _ring_queue[c_step];
        // 维持环形队列
        c_step++;
        c_step %= _cap;
        V(p_space);

        Unlock(c_lock);
    }

private:
    std::vector<T> _ring_queue;
    int _cap = 0;
    int c_step = 0; // 消费者下标
    int p_step = 0; // 生产者下标

    sem_t c_data;  // 消费者关注剩余数据
    sem_t p_space; // 生产者关注剩余空间

    pthread_mutex_t p_lock; // 生产者多线程间互斥访问环形队列
    pthread_mutex_t c_lock; // 消费者多线程间互斥访问环形队列
};

demo-1

#include "RingQueue.hpp"

void *Productor(void *args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
    int i=0;
    while (1)
    {
        cout << "product a data:" << i<<endl;
        rq->Push(i);
        ++i;
        //cout<<"6666"<<endl;
        sleep(1);
    }
}
void *Consumer(void *args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
    while (1)
    {
        sleep(1);
        int data=-1;
        rq->Pop(&data);
        cout << "consumer a data:" <<data<< endl;
    }
}
int main()
{
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_t c, p;
    pthread_create(&c, nullptr, Consumer, rq);
    pthread_create(&p, nullptr, Productor, rq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}

demo-2

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include "RingQueue.hpp"
#include "Task.hpp"

using namespace std;
struct ThreadData
{
    RingQueue<Task> *rq;
    std::string threadname;
};

void *Productor(void *args)
{
    // sleep(3);
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    std::string name = td->threadname;
    int len = opers.size();
    while (true)
    {
        // 1. 获取数据
        int data1 = rand() % 10 + 1;
        usleep(10);
        int data2 = rand() % 10;
        char op = opers[rand() % len];
        Task t(data1, data2, op);

        // 2. 生产数据
        rq->Push(t);
        cout << "Productor task done, task is : " 
        << t.GetTask() << " who: " << name << endl;

        sleep(1);
    }
    return nullptr;
}

void *Consumer(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    std::string name = td->threadname;

    while (true)
    {
        // 1. 消费数据
        Task t;
        rq->Pop(&t);
       
        // 2. 处理数据
        t();
        cout << "Consumer get task, task is : " 
        << t.GetTask() << " who: " 
        << name << " result: " << t.GetResult() << endl;
        // sleep(1);

    }
    return nullptr;
}

int main()
{
    srand(time(nullptr) ^ getpid());
    RingQueue<Task> *rq = new RingQueue<Task>(50);

    pthread_t c[5], p[3];

    for (int i = 0; i < 3; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Productor-" + std::to_string(i);

        pthread_create(p + i, nullptr, Productor, td);
    }
    for (int i = 0; i < 5; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Consumer-" + std::to_string(i);

        pthread_create(c + i, nullptr, Consumer, td);
    }

    for (int i = 0; i < 3; i++)
    {
        pthread_join(p[i], nullptr);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(c[i], nullptr);
    }

    return 0;
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值