acl-dev / acl

C/C++ server and network library, including coroutine,redis client,http/https/websocket,mqtt, mysql/postgresql/sqlite client with C/C++ for Linux, Android, iOS, MacOS, Windows, etc..
https://acl-dev.cn
GNU Lesser General Public License v3.0
2.88k stars 936 forks source link

multi thread的例子在windows下会运行出错 #258

Closed alongL closed 2 years ago

alongL commented 2 years ago

在linux下不会出错。 源码用的是https://blog.csdn.net/zsxxsz/article/details/89007127?spm=1001.2014.3001.5501 第四点”使用多核“ 的那个例子。

出错的位置: event_iocp.c

image

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>

// 客户端协程处理类,用来回显客户发送的内容,每一个客户端连接绑定一个独立的协程
class fiber_echo : public acl::fiber
{
public:
    fiber_echo(acl::socket_stream* conn) : conn_(conn) {}
private:
    acl::socket_stream* conn_;
    ~fiber_echo(void) { delete conn_; }
    // @override
    void run(void) {
        char buf[8192];
        while (true) {
            int ret = conn_->read(buf, sizeof(buf), false);
            if (ret == -1) {
                break;
            }
            if (conn_->write(buf, ret) != ret) {
                break;
            }
        }
        delete this; // 自销毁动态创建的协程对象
    }
};

// 独立的协程过程,接收客户端连接,并将接收的连接与新创建的协程进行绑定
class fiber_listen : public acl::fiber
{
public:
    fiber_listen(acl::server_socket& listener) : listener_(listener) {}
private:
    acl::server_socket& listener_;
    ~fiber_listen(void) {}
    // @override
    void run(void) {
        while (true) {
            acl::socket_stream* conn = listener_.accept();  // 等待客户端连接
            if (conn == NULL) {
                printf("accept failed: %s\r\n", acl::last_serror());
                break;
            }
            // 创建并启动单独的协程处理客户端连接
            acl::fiber* fb = new fiber_echo(conn);
            fb->start();
        }
        delete this;
    }
};

// 独立的线程调度类
class thread_server : public acl::thread
{
public:
    thread_server(acl::server_socket& listener) : listener_(listener) {}
    ~thread_server(void) {}
private:
    acl::server_socket& listener_;
    // @override
    void* run(void) {
        // 创建并启动独立的监听协程,接受客户端连接
        acl::fiber* fb = new fiber_listen(listener_);
        fb->start();
        // 启动协程调度器
        acl::fiber::schedule(); // 内部处于死循环过程
        return NULL;
    }
};

int main(void)
{
    const char* addr = "127.0.0.1:8000";
    acl::server_socket listener;
    // 监听本地地址
    if (listener.open(addr) == false) {
        printf("listen %s error %s\r\n", addr, acl::last_serror());
        return 1;
    }

    std::vector<acl::thread*> threads;
    // 创建多个独立的线程对象,每个线程启用独立的协程调度过程
    for (int i = 0; i < 4; i++) {
        acl::thread* thr = new thread_server(listener);
        threads.push_back(thr);
        thr->start();
    }
    for (std::vector<acl::thread*>::iterator it = threads.begin();
        it != threads.end(); ++it) {
        (*it)->wait();
        delete* it;
    }
    return 0;
}

#ifdef WIN32
class WSInit
{
public:
    WSInit()
    {
        WSADATA wsadata;
        WSAStartup(MAKEWORD(2, 2), &wsadata);
    }

    ~WSInit() { WSACleanup(); }
};

static WSInit wsinit_;
#endif
alongL commented 2 years ago

改成这样在windows下运行没有问题。 就是几个线程同时监听同一个地址。但使用不同的acl::server_socket对象。

#include <acl-lib/acl_cpp/lib_acl.hpp>
#include <acl-lib/fiber/libfiber.hpp>
#include <acl-lib/acl/stdlib/acl_sys_patch.h>

// 客户端协程处理类,用来回显客户发送的内容,每一个客户端连接绑定一个独立的协程
class fiber_echo : public acl::fiber
{
public:
    explicit fiber_echo(acl::socket_stream* conn) : conn_(conn) {}
private:
    acl::socket_stream* conn_;
    ~fiber_echo() override { delete conn_; }
    // @override
    void run() override {
        char buf[8192];
        while (true) {
            int ret = conn_->read(buf, sizeof(buf), false);
            if (ret == -1) {
                break;
            }
            if (conn_->write(buf, ret) != ret) {
                break;
            }
        }
        delete this; // 自销毁动态创建的协程对象
    }
};

// 独立的协程过程,接收客户端连接,并将接收的连接与新创建的协程进行绑定
class fiber_listen : public acl::fiber
{
public:
    explicit fiber_listen(acl::server_socket& listener) : listener_(listener) {}
private:
    acl::server_socket& listener_;
    ~fiber_listen() override = default;
    // @override
    void run() override {
        while (true) {
            acl::socket_stream* conn = listener_.accept();  // 等待客户端连接
            if (conn == nullptr) {
                printf("accept failed: %s\r\n", acl::last_serror());
                break;
            }
            printf("new client : %s\r\n", conn->get_peer_ip());
            // 创建并启动单独的协程处理客户端连接
            acl::fiber* fb = new fiber_echo(conn);
            fb->start();
        }
        delete this;
    }
};

// 独立的线程调度类
class thread_server : public acl::thread
{
public:
    explicit thread_server()  {

    }
    ~thread_server() override = default;
private:
    acl::server_socket listener_;
    // @override
    void* run() override {
            const char* addr = "127.0.0.1:8000";
            acl::server_socket listener;
            // 监听本地地址
            if (!listener.open(addr)) {
                printf("listen %s error %s\r\n", addr, acl::last_serror());
                return NULL;
            }

        // 创建并启动独立的监听协程,接受客户端连接
        acl::fiber* fb = new fiber_listen(listener);
        fb->start();
        // 启动协程调度器
        acl::fiber::schedule(); // 内部处于死循环过程
        return NULL;
    }
};

int main(void)
{
    acl_socket_init();

    std::vector<acl::thread*> threads;
    // 创建多个独立的线程对象,每个线程启用独立的协程调度过程
    for (int i = 0; i < 4; i++) {
        acl::thread* thr = new thread_server();
        threads.push_back(thr);
        thr->start();
    }

    for (auto& thread : threads) {
        thread->wait();
        delete thread;
    }
    getchar();
    return 0;
}

acl用着感觉还是不错的样子。

zhengshuxin commented 2 years ago

是的,那篇文章给出的多线程中的例子存在一些问题,因为 acl fiber 是单线程调试方式,所以同一 socket 句柄不能在多个线程中的协程中使用,应该如你上面给出的例子那样,每个线程创建一个监听对象才可以;另外,我也专门写了一个示例来展示在多线程协程下,如何通过 acl::fiber_tbox来传递连接对象的情况,该示例位置:lib_fiber\samples-c++1x\server-threads ,在该例子中,在一个独立的线程(非协程模式)中接收连接,通过每个协程模式的线程中的 fiber_tbox 将连接对象传递给相应的协程。 非常感谢你指出的错误,我将会修正文章中的错误。 --zsx

alongL commented 2 years ago

感谢回复! 那在linux下并未报错,但应该也不能这么用吧?

zhengshuxin commented 2 years ago

是的,在linux下也不该那样使用。