【问题标题】:How to deal with racing threads (C++; TinyThread++)?如何处理赛车线程(C++;TinyThread++)?
【发布时间】:2012-11-10 18:37:22
【问题描述】:

这是主要代码的示例(“Library/stack.h”并不重要,但无论如何,它是this previous question of mine中包含的最后一个源):

#include <stdlib.h>
#include <time.h>

#include <iostream>
#include <tinythread.h>

#include "Library/stack.h"

using namespace std;
using namespace tthread;

#define BOULDERspd 100

// ========================================================================= //

struct Coord {
    int x, y;
};

int randOneIn (float n) {
    return ((int) (n * (rand() / (RAND_MAX + 1.0))));
}
int randOneIn (int n) {
    return ((int) ((float) n * (rand() / (RAND_MAX + 1.0))));
}

// ========================================================================= //

#include <windows.h>
void gotoxy (int column, int line) {
    if ((column >= 0) && (line >= 0)) {
        COORD coord;
        coord.X = column;
        coord.Y = line;
        SetConsoleCursorPosition(
            GetStdHandle( STD_OUTPUT_HANDLE ),
            coord
        );
    }
}

void gotoxy (Coord pos) {
    gotoxy(pos.x, pos.y);
}

// ========================================================================= //

void render (char image, Coord pos) {
    gotoxy(pos);
    cout << image;
}

void unrender (Coord pos) {
    gotoxy(pos);
    cout << ' ';
}

// ========================================================================= //

char randimage (void) {
    return (rand() % 132) + 123;
}

mutex xylock;

class Boulder {
    char avatar;
    Coord pos;

    public:
        Boulder (int inix) {
            pos.x = inix;
            pos.y = 0;

            avatar = randimage();
        };

        void fall (void) {

            unrender(pos);
            pos.y++;
            render(avatar, pos);

            Sleep(BOULDERspd);
        };

        void live (void) {
            do {
                fall();
            } while (y() < 20);
            die();
        };

        void die (void) {
            unrender(pos);
            pos.y = 0;
        };

        int x (void) { return pos.x; };
        int y (void) { return pos.y; };
};

// ========================================================================= //

class thrStack: public Stack<thread*> {
    public:
        thrStack (): Stack<thread*> () { };

        void pushNrun (thread* elem) {
            push(elem);
            top->core->joinable();
        }
};

void randBoulder (void* arg) {
    srand(time(NULL));
    Boulder boulder(rand() % 40);

    boulder.live();
}

void Boulders (void* arg) {
    srand(time(NULL));
    thrStack stack;

    do {
        stack.pushNrun(new thread (randBoulder, 0));
        Sleep(rand() % 300);
    } while(1);
}

// ========================================================================= //
// ========================================================================= //

int main() {
    thread raining (Boulders, 0);

    raining.join();
}

我是多线程的新手,所以为了摆弄它,我正在尝试制作一个程序,让随机字符不断从屏幕顶部掉下来,就好像在下雨一样 ASCII 符号。

不过,我注意到我的编码中有一点(大)错误:

bool xylock = false;

class Boulder {
    char avatar;
    Coord pos;

    public:
        Boulder (int inix) {
            pos.x = inix;
            pos.y = 0;

            avatar = randimage();
        };

        void fall (void) {

            unrender(pos);
            pos.y++;
            render(avatar, pos);

            Sleep(BOULDERspd);
        };

        void live (void) {
            do {
                fall();
            } while (y() < 20);
            die();
        };

        void die (void) {
            unrender(pos);
            pos.y = 0;
        };

        int x (void) { return pos.x; };
        int y (void) { return pos.y; };
};

因为 fall() 函数使用了 gotoxy,它会改变“全局光标”,多次调用 gotoxy 会打乱程序的预期执行。如果您尝试按原样编译代码,您会得到不断变换位置并留下垃圾的掉落字母。

有没有什么方法可以使用或实现一个锁来解决这个和未来的情况,就像 TinyThread 一样? C++中锁的实现逻辑一般是什么?


编辑:修改 fall();没事吧,驯鹿?

        void fall (void) {
            lock_guard<mutex> guard(xylock);

            unrender(pos);
            pos.y++;
            render(avatar, pos);

            xylock.unlock();

            Sleep(BOULDERspd);
        };

【问题讨论】:

  • 你从哪里开始线程?我是否遗漏了您的代码中的某些内容?
  • @marscode 在 main() (在第一个源中找到,但在后者中没有),我创建了一个调用 void Boulders (void* arg) 函数的线程。该函数依次实例化一个线程堆栈(通过 thrStack 堆栈),并不断将调用 void randboulder 的新线程推入该堆栈以供它们运行。

标签: c++ multithreading locking race-condition


【解决方案1】:

您可以使用 tinythread 库:

http://tinythreadpp.bitsnbites.eu/doc/

具体看lock_guardmutex

多次调用 gotoxy 会打乱预期的执行 程序。如果您尝试按原样编译代码,您会摔倒 不断切换位置并留下垃圾的字母 自己落后。

创建一个mutex 对象以进行同步,然后在您希望线程安全的函数中使用它创建一个本地lock_guard。这个mutex 可以在多个地方使用,也可以使用lock_guard

【讨论】:

  • 我修改了fall()函数中的代码;现在好了吗?
  • 说实话,不。但我是否至少实现了理想?
  • @Mutoh 是的,这就是使用它的方式。让我稍微回顾一下代码,看看是否还有其他我能看到的东西。
  • @Mutoh 我认为renderunrender 可能是要同步的点,fall 方法是巨石的本地方法-如果每个巨石实例的mutex 不同,那么它不会保护修改 x,y 坐标的底层函数
【解决方案2】:

在这里,我创建了一个非常基本的线程示例,没有框架或类。如您所见,线程和同步不是 C++ 工作,而是操作系统工作! ;-)

在这里我创建了一个简单的线程函数,我调用了两次。线程写入相同的变量,但不能同时执行,所以必须保护它。在此示例中,我使用 CRITICAL_SECTION 对象通过一个线程锁定变量。如果一个线程锁定它,另一个线程无法访问它,必须等待它空闲。

仔细看看,我还保护了 printf 操作。如果你不这样做会发生什么?你会得到一个非常有趣的印记!找出原因,你就会知道线程和锁是如何工作的。 :-)

#include <windows.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <process.h>

//a global variable (just do do someting):
int g_ThreadCounter = 0;


//global variable to end the threads:
bool g_Run = true;
//do not use global variables, there are better solutions! I just did it here to 
//keep it simple and focus on the issue!

//a critical section object - something like a "soft-version" of a mutex to synchronize
//write access on variables
CRITICAL_SECTION critical;
//a thread function
unsigned  __stdcall threadFunc(void *pThreadNum)
{

    unsigned int iThreadNum = reinterpret_cast<unsigned int>(pThreadNum);

    do{
    //you need the critical section only when you change values:
    EnterCriticalSection(&critical);
        g_ThreadCounter++;

        printf("from thread: ");
        printf("%d", iThreadNum);
        printf(" counter = ");      
        printf("%d", g_ThreadCounter);
        printf("\n");
    LeaveCriticalSection(&critical);

    //sleep a secound
    Sleep (1000);
}while(g_Run);

_endthreadex(0);
return 0;
}

int main()
{
unsigned int ThreadID1 = 1;

unsigned int ThreadID2 = 2;
//initialize the critical section with spin count (can be very effective in case
//of short operation times, see msdn for more information)
if(!InitializeCriticalSectionAndSpinCount(&critical, 1000))
{
    //DO NOT START THE THREADS, YOU DON'T HAVE SYNCHRONISATION!!!
    printf("someting went wrong, press any key to exit");
    //do some error handling
    getchar();
    exit(-1);
}

//start the threads
HANDLE thHandle1 = (HANDLE)_beginthreadex(NULL, 0, &threadFunc, (void*) ThreadID1, 0, NULL);

HANDLE thHandle2 = (HANDLE)_beginthreadex(NULL, 0, &threadFunc, (void*) ThreadID2, 0, NULL);

if(thHandle1 == INVALID_HANDLE_VALUE || thHandle2 == INVALID_HANDLE_VALUE)
{
    printf("something went wrong, press any key to exit");
    //do some error handling
    getchar();
    exit(-1);
}
//the main thread sleeps while the other threads are working
Sleep(5000);

//set the stop variable
EnterCriticalSection(&critical);
    g_Run = false;
LeaveCriticalSection(&critical);

//wait for the thread; infinite means, you wait as long as the 
    //thread takes to finish
WaitForSingleObject(thHandle1, INFINITE);
CloseHandle(thHandle1);

WaitForSingleObject(thHandle2, INFINITE);
CloseHandle(thHandle2);

DeleteCriticalSection(&critical);

printf("press any key to exit");
getchar();

return 0;
}

研究您正在使用的操作系统!有时总比过分关注框架和外来类要好。这样可以解决很多问题!

【讨论】:

  • 假设在 Enter 和 Leave Critical 部分之间有一些东西会引发异常(您没有在 catch 中处理)。除非您使用像 RAII (lock_guard) 这样的技术,否则您将锁定该部分,这意味着您的应用程序可能无法在不重新启动的情况下恢复。框架在 BTW 下使用这些操作系统功能...
  • 这只是一个简单的示例来展示锁的工作原理!当然,您必须处理死锁!每次 try/chatch 或 return 都需要释放临界区!
  • 是的,但我的观点(以及为什么 C++ 有用)是通过使用 lock_guard 可以处理它,即使您遇到意外的异常 - I.E.一个你没有编写代码来处理的:)
  • 这是一件非常好的事情,当我编写商业代码时,我也在使用框架,因为它们更节省。但是您需要了解为什么事情会以这种方式发生。这是初学者使用框架时的常见问题! ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-25
相关资源
最近更新 更多