使用带堆栈的线程(C++)

Using Threads With A Stack (C++)

本文关键字:C++ 线程 堆栈      更新时间:2023-10-16

我试图使用Threads创建一个堆栈,我的代码:

推送函数(m为互斥)

void Stack::Push(int num){  
    m.lock();
    sta[current++] = num;
    m.unlock();
}

弹出功能:

int Stack::Pop(){
    if (current > 0){
        m.lock();
        int a = sta[--current];
        m.unlock();
        return a;
    }
    return sta[0];
}

主要:

void threadsPrint(string s){
    m1.lock();
    cout << s << endl;
    m1.unlock();
}
void func(Stack & t){
    threadsPrint("T1 Push 1n");
    t.Push(1);
    threadsPrint("T1 Push 2n");
    t.Push(2);
    threadsPrint("T1 Push 3n");
    t.Push(3);
    threadsPrint("T1 Push 4n");
    t.Push(4);
    threadsPrint("T1 Push 5n");
    t.Push(5);
    threadsPrint("T1 Push 6n");
    t.Push(6);
}
void func2(Stack & t){
    threadsPrint("T2 Pop "+to_string(t.Pop())+"n");
    threadsPrint("T2 Pop " + to_string(t.Pop()) + "n");
    threadsPrint("T2 Pop " + to_string(t.Pop()) + "n");
    threadsPrint("T2 Pop " + to_string(t.Pop()) + "n");
    threadsPrint("T2 Pop " + to_string(t.Pop()) + "n");
    threadsPrint("T2 Pop " + to_string(t.Pop()) + "n");
}
int main(){
    Stack t;
    thread t1(func,ref(t));
    thread t2(func2,ref(t));
    t1.join();
    t2.join();
    return 0;
}

输出:

   T1 Push 1
   T2 Pop -842150451
   T1 Push 2
   T2 Pop 1
   T1 Push 3
   T2 Pop 2
   T1 Push 4
   T2 Pop 3
   T1 Push 5
   T2 Pop 4
   T1 Push 6
   T2 Pop 5

我知道这是糟糕的代码,但我只是尝试使用线程我仍然没有得到正确的结果,我该怎么修复代码?

if current > 0){
    m.lock();

不能在m.lock()之外检查current。受比赛条件限制。

int Stack::Pop(){
  m.lock();
  int a = current ? sta[--current] : sta[0];
  m.unlock();
  return a;
}

但是,您的堆栈从根本上仍然无法区分弹出最后一项还是在空堆栈上弹出。就我个人而言,我更喜欢这样的东西:

boolean Stack::Pop(int& val){
  boolean ret = false;
  m.lock();
  if (current) {
     val = sta[--current];
     ret = true;
  }
  m.unlock();
  return ret;
}

当堆栈为空时,等待堆栈增长的问题将委派给调用方。具有等待和信号的成熟的生产者-消费者堆栈超出了本文的范围。

当然,lock()/unlock()应该是RAII。

我认为除了互斥锁之外,您可能还想使用一个条件变量,这样,如果堆栈上没有任何东西,Pop函数就会等待推送。

在Push()中可以调用cv.notify_one();,在Pop()中则可以调用cv.wait(m, []{return current > 0;});

请参阅此处的示例:http://en.cppreference.com/w/cpp/thread/condition_variable

一种可能的交错,它会给出您看到的前几个值:

T1                                   T2
threadsPrint("T1 Push 1n");
                                     threadsPrint("T2 Pop "+to_string(t.Pop())+"n");
t.Push(1);

当T2执行t.Pop()时,您将得到一个垃圾值,而T1在推送之前打印了它的输出,因此跟踪是错误的。

要修复跟踪以便更好地反映操作,您需要将跟踪输出移动到锁内(或添加另一个锁)
要修复"弹出错误",您需要等到堆栈上有内容后再弹出任何内容。

根据我的观察,代码容易出现竞争条件,即程序的输出取决于指令的调度。

看看您的代码,让我们考虑一下这个调度场景。

第一个线程1被调度,它进入func。

打印"T1推送1"

上下文在进入推送例程之前切换到T2。现在的保持0并且没有任何内容写入。

打印"T2 Pop"并呼叫Pop。现在做--current将使您访问堆栈[0],即垃圾值。如果堆栈是全局的,则为零,否则为某个错误值。

上下文切换回t1并继续"t1推送2",依此类推

因此,你得到的输出。它可能会多次给出正确的输出,但如果调度发生不同,则会给出错误的输出。

因此,当您的pop线程到达当前的0而不是返回堆栈[0](这可能是垃圾)时,明智的做法是等待某个东西被推入堆栈。

我已经使用等待和pthreads信号机制编写了代码。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <stack>
using namespace std;
pthread_mutex_t mutex;
pthread_cond_t condv;
pthread_mutexattr_t mattr;
pthread_condattr_t cattr;
int  FLAG= 0; //for wait and notify.
int current = 0;
int s[100] ; //stack

void push(int num)
{
    pthread_mutex_lock(&mutex);
    cout<<"push: curr "<<current<<endl;
    s[current++] = num;
    FLAG = 1; //Done pushing. Set flag.
    pthread_cond_signal(&condv); //notify other thread
    pthread_mutex_unlock(&mutex); // release mutex
}
int pop()
{
    int a = -1;
    pthread_mutex_lock(&mutex);
    while(FLAG == 0) {
        pthread_cond_wait(&condv,&mutex); //wait until the FLAG is unset.
    }
    if(current > 0)
    {
        cout<<"pop: curr "<<current<<endl;
        a = s[--current];
        if(current == 0) //If you're removing the last element of stack unset FLAG.
            FLAG = 0;
        pthread_mutex_unlock(&mutex);
        return a;
    }
    pthread_mutex_unlock(&mutex);
    return a;
}
void* t1(void *arg)
{
    cout<<"pushing 1n";
    push(1);
    cout<<"pushing 10n";
    push(10);
    cout<<"pushing 12n";
    push(12);
    cout<<"pushing 4n";
    push(4);
    cout<<"pushing 6n";
    push(6);
    cout<<"pushing 7n";
    push(7);
}
void* t2(void *arg)
{
    cout<<"Popped "<<pop()<<endl;
    cout<<"Popped "<<pop()<<endl;
    cout<<"Popped "<<pop()<<endl;
    cout<<"Popped "<<pop()<<endl;
    cout<<"Popped "<<pop()<<endl;
    cout<<"Popped "<<pop()<<endl;
}

int main()
{
    pthread_t thread1, thread2;
    pthread_mutexattr_init(&mattr);
    pthread_mutex_init(&mutex, &mattr); 
    pthread_condattr_init(&cattr);
    pthread_cond_init(&condv, &cattr);  
    pthread_create(&thread1, NULL, &t1, NULL);
    pthread_create(&thread2, NULL, &t2, NULL);

    pthread_mutexattr_destroy(&mattr);
    pthread_condattr_destroy(&cattr);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
return 0;
}