0%

二叉树的三种遍历的非递归实现

前序遍历

  • 主要思路是先将根节点入栈,然后每次循环从栈顶取出元素输出,同时将该元素的右节点左节点压入栈中
    class Solution {
    public:
    vector<int> preorderTraversal(TreeNode* root) {
    if(!root)return vector<int>();

    stack<TreeNode*> s;
    s.push(root);
    TreeNode* temp;
    vector<int> ret;
    while(!s.empty())
    {
    temp = s.top();
    s.pop();
    ret.push_back(temp->val);
    if(temp->right)s.push(temp->right);
    if(temp->left)s.push(temp->left);
    }
    return ret;
    }
    };

    中序遍历

  • 主要思路是新建一个结构体,结构体包括二叉树的节点指针信息和是否被访问过的标记信息,标记默认是false
    typedef struct nodeStruct
    {
    TreeNode* p;
    bool flg;
    nodeStruct(TreeNode* ptr):p(ptr),flg(false){}
    nodeStruct(TreeNode* ptr, bool flag):p(ptr),flg(flag){}
    } nodeS;
  • 每次从栈顶取出一个元素,如果该元素的标记提示被访问过,那么直接将元素输出
  • 如果该元素没有被访问过,那么现将该元素的访问标记置为true,然后将该元素的右子节点压入栈中,然后将节点自己压入栈中,最后将左子节点压入栈中
    typedef struct nodeStruct
    {
    TreeNode* p;
    bool flg;
    nodeStruct(TreeNode* ptr):p(ptr),flg(false){}
    nodeStruct(TreeNode* ptr, bool flag):p(ptr),flg(flag){}
    } nodeS;

    class Solution {
    public:
    vector<int> inorderTraversal(TreeNode* root) {
    if(!root)return vector<int>();
    vector<int> ret;
    stack<nodeS> s;
    s.push(nodeS(root));
    nodeS temp(NULL);
    while(!s.empty())
    {
    temp = s.top();
    s.pop();
    if(!temp.flg)
    {
    if(temp.p->right)s.push(nodeS(temp.p->right));
    temp = nodeS(temp.p, true);
    s.push(temp);
    if(temp.p->left)s.push(nodeS(temp.p->left));
    }
    else
    {
    ret.push_back(temp.p->val);
    }
    }
    return ret;
    }
    };

##后序遍历

  • 结构体方法同上,跟之前的主要区别是针对没有访问过的节点需要先将自己压入栈中,然后再将右节点左节点压入栈中
    typedef struct
    stackUnit{
    TreeNode* p;
    bool flg;
    stackUnit(TreeNode* ptr, bool flag):p(ptr), flg(flag){}
    } nodeS;
    class Solution {
    public:
    vector<int> postorderTraversal(TreeNode* root) {
    if(!root)return vector<int>();
    vector<int> v;
    stack<nodeS> s;
    s.push(nodeS(root, false));
    nodeS temp(NULL, false);
    while(!s.empty())
    {
    temp = s.top();
    s.pop();
    if(temp.flg)
    {
    v.push_back(temp.p->val);
    }
    else
    {
    s.push(nodeS(temp.p, true));
    if(temp.p->right)s.push(nodeS(temp.p->right,false));
    if(temp.p->left)s.push(nodeS(temp.p->left,false));
    }
    }
    return v;
    }
    };

分析基于epoll的C++高性能webServer代码(三)

epoll用法参考

Socket类

  • Socket类基于C语言的tcp通信库实现,将C语言提供的Socket相关操作函数利用C++的面向对象进行了包装

  • 成员数据对象只有一个int serverfd_;

构造函数Socket(/* args */)

  • 创建socket对象
Socket::Socket(/* args */)
{
serverfd_ = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == serverfd_)
{
perror("socket create fail!");
exit(-1);
}
std::cout << "server create socket" << serverfd_ << std::endl;
}

析构函数

  • 关闭socket文件描述符
Socket::~Socket()
{
close(serverfd_);
std::cout << "server close..." << std::endl;
}

SetReuseAddr()

  • 设置地址
void Socket::SetReuseAddr()
{
int on = 1;
setsockopt(serverfd_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
}

Setnonblocking()

  • 设置为非阻塞模式
void Socket::Setnonblocking()
{
int opts = fcntl(serverfd_, F_GETFL);
if (opts<0)
{
perror("fcntl(serverfd_,GETFL)");
exit(1);
}
if (fcntl(serverfd_, F_SETFL, opts | O_NONBLOCK) < 0)
{
perror("fcntl(serverfd_,SETFL,opts)");
exit(1);
}
std::cout << "server setnonblocking..." << std::endl;
}

BindAddress(int serverport)

  • 将主机对应的端口号与socket进行绑定
bool Socket::BindAddress(int serverport)
{
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//inet_addr(_ServerIP.c_str());
serveraddr.sin_port = htons(serverport);
int resval = bind(serverfd_, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
if (resval == -1)
{
close(serverfd_);
perror("error bind");
exit(1);
}
std::cout << "server bindaddress..." << std::endl;
return true;
}

Listen()

  • 就是调用C提供的Listen函数
bool Socket::Listen()
{
if (listen(serverfd_, 2048) < 0)
{
perror("error listen");
close(serverfd_);
exit(1);
}
std::cout << "server listenning..." << std::endl;
return true;
}

Accept(struct sockaddr_in &clientaddr)

  • 调用C提供的Accept()函数
  • 返回用户socket的文件描述符
int Socket::Accept(struct sockaddr_in &clientaddr)
{
socklen_t lengthofsockaddr = sizeof(clientaddr);
int clientfd = accept(serverfd_, (struct sockaddr*)&clientaddr, &lengthofsockaddr);
if (clientfd < 0)
{
//perror("error accept");
//if(errno == EAGAIN)
//return 0;
//std::cout << "error accept:there is no new connection accept..." << std::endl;
return clientfd;
}
//std::cout << "server accept,clientfd: " << clientfd << std::endl;
return clientfd;
}

Close()

  • 关闭对应的文件描述符
bool Socket::Close()
{
close(serverfd_);
std::cout << "server close..." << std::endl;
return true;
}

Epoll介绍

等待队列

  • 当进程A执行到创建socket的语句时,操作系统会创建一个由文件系统管理的socket对象(如下图)。这个socket对象包含了发送缓冲区、接收缓冲区、等待队列等成员。等待队列是个非常重要的结构,它指向所有需要等待该socket事件的进程

img

  • 当程序执行到recv时,操作系统会将进程A从工作队列移动到该socket的等待队列中(如下图)。由于工作队列只剩下了进程B和C,依据进程调度,cpu会轮流执行这两个进程的程序,不会执行进程A的程序。所以进程A被阻塞,不会往下执行代码,也不会占用cpu资源

img

  • ps:操作系统添加等待队列只是添加了对这个“等待中”进程的引用,以便在接收到数据时获取进程对象、将其唤醒,而非直接将进程管理纳入自己之下。上图为了方便说明,直接将进程挂到等待队列之下。
  • 当socket接收到数据后,操作系统将该socket等待队列上的进程重新放回到工作队列,该进程变成运行状态,继续执行代码。也由于socket的接收缓冲区已经有了数据,recv可以返回接收到的数据

内核接收网络数据全过程

  • 如下图所示,进程在recv阻塞期间,计算机收到了对端传送的数据(步骤①)。数据经由网卡传送到内存(步骤②),然后网卡通过中断信号通知cpu有数据到达,cpu执行中断程序(步骤③)。此处的中断程序主要有两项功能,先将网络数据写入到对应socket的接收缓冲区里面(步骤④),再唤醒进程A(步骤⑤),重新将进程A放入工作队列中。

img

  • 一个socket对应着一个端口号,而网络数据包中包含了ip和端口的信息,内核可以通过端口号找到对应的socket。当然,为了提高处理速度,操作系统会维护端口号到socket的索引结构,以快速读取。

同时监视多个socket的简单方法

  • 服务端需要管理多个客户端连接,而recv只能监视单个socket,这种矛盾下,人们开始寻找监视多个socket的方法。epoll的要义是高效的监视多个socket。从历史发展角度看,必然先出现一种不太高效的方法,人们再加以改进。只有先理解了不太高效的方法,才能够理解epoll的本质。
  • 假如能够预先传入一个socket列表,如果列表中的socket都没有数据,挂起进程,直到有一个socket收到数据,唤醒进程。这种方法很直接,也是select的设计思想。
  • 为方便理解,我们先复习select的用法。在如下的代码中,先准备一个数组(下面代码中的fds),让fds存放着所有需要监视的socket。然后调用select,如果fds中的所有socket都没有数据,select会阻塞,直到有一个socket接收到数据,select返回,唤醒进程。用户可以遍历fds,通过FD_ISSET判断具体哪个socket收到数据,然后做出处理。
int s = socket(AF_INET, SOCK_STREAM, 0);  
bind(s, ...)
listen(s, ...)

int fds[] = 存放需要监听的socket

while(1){
int n = select(..., fds, ...)
for(int i=0; i < fds.count; i++){
if(FD_ISSET(fds[i], ...)){
//fds[i]的数据处理
}
}
}

select的流程

  • select的实现思路很直接。假如程序同时监视如下图的sock1、sock2和sock3三个socket,那么在调用select之后,操作系统把进程A分别加入这三个socket的等待队列中。

img

  • 当任何一个socket收到数据后,中断程序将唤起进程。下图展示了sock2接收到了数据的处理流程。

img

  • 所谓唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面。如下图所示。

img

  • 经由这些步骤,当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就可以得到就绪的socket。

  • 这种简单方式行之有效,在几乎所有操作系统都有对应的实现。

select的缺点

  • 其一,每次调用select都需要将进程加入到所有监视socket的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正是因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket。
  • 其二,进程被唤醒后,程序并不知道哪些socket收到数据,还需要遍历一次。

epoll的设计思路

  • epoll是在select出现N多年后才被发明的,是select和poll的增强版本。epoll通过以下一些措施来改进效率。
  • select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。如下图所示,每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程。显而易见的,效率就能得到提升。

图 1

  • 为方便理解后续的内容,我们先复习下epoll的用法。如下的代码中,先用epoll_create创建一个epoll对象epfd,再通过epoll_ctl将需要监视的socket添加到epfd中,最后调用epoll_wait等待数据。
    int s = socket(AF_INET, SOCK_STREAM, 0);   
    bind(s, ...)
    listen(s, ...)

    int epfd = epoll_create(...);
    epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中

    while(1){
    int n = epoll_wait(...)
    for(接收到数据的socket){
    //处理
    }
    }
  • select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。

图 2

epoll的原理和流程

  • 创建epoll对象
    • 如下图所示,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(也就是程序中epfd所代表的对象)。eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
    • 图 3
    • 创建一个代表该epoll的eventpoll对象是必须的,因为内核要维护“就绪列表”等数据,“就绪列表”可以作为eventpoll的成员。
  • 维护监视列表
    • 创建epoll对象后,可以用epoll_ctl添加或删除所要监听的socket。以添加socket为例,如下图,如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的等待队列中。
    • 图 4
    • 当socket收到数据后,中断程序会操作eventpoll对象,而不是直接操作进程。
  • 接收数据
    • 当socket收到数据后,中断程序会给eventpoll的“就绪列表”添加socket引用。如下图展示的是sock2和sock3收到数据后,中断程序让rdlist引用这两个socket。
    • 图 5
    • eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。
    • 当程序执行到epoll_wait时,如果rdlist已经引用了socket,那么epoll_wait直接返回,如果rdlist为空,阻塞进程。

Channel类

  • 该类主要用于处理服务器用到的epoll的各种事件以及该做出反应
  • 主要内容包括设置各种回调函数的操作函数
  • void SetReadHandle(Callback cb), void SetWriteHandle(Callback cb), void SetErrorHandle(Callback cb), void SetCloseHandle(Callback cb)
  • 另一个是epoll事件的处理函数,负责检测epoll读、写、对方关闭或者连接错误等情况
    void Channel::HandleEvent()
    {
    if(events_ & EPOLLRDHUP)//对方异常关闭事件
    {
    std::cout << "Event EPOLLRDHUP" << std::endl;
    closehandler_();
    }
    else if(events_ & (EPOLLIN | EPOLLPRI))//读事件,对端有数据或者正常关闭
    {
    //std::cout << "Event EPOLLIN" << std::endl;
    readhandler_();
    }
    else if(events_ & EPOLLOUT)//写事件
    {
    std::cout << "Event EPOLLOUT" << std::endl;
    writehandler_();
    }
    else
    {
    std::cout << "Event error" << std::endl;
    errorhandler_();//连接错误
    }
    }

Poller

  • 这个类使用面向对象的方式封装了C传统提供的epoll相关的方法

    构造函数 Poller(/* args */)

  • 使用初始化列表初始化了一个内容为epoll_eventvector,其中epoll_event的结构体定义为

    struct epoll_event
    {
    uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
    } __EPOLL_PACKED;
  • 初始化列表创建了一个映射关系为<int, Channel*>的map对象

  • 然后调用epoll_create函数创建epoll对象, epoll_create(int)函数的参数含义是监听socket的数量

    poll(ChannelList &activechannellist)

  • 等待IO事件的函数

  • 开始使用epoll_wait函数等待IO事件

    int nfds = epoll_wait(pollfd_, &*eventlist_.begin(), (int)eventlist_.capacity(), timeout);
  • 然后就是从等待得到的事件列表中逐个遍历处理,包括从事件数组中取出数据、寻找数据对应的文件描述符,找到之后通过Channel对象的SetEvents设置事件类型,同时将将其加入activechannellist。如果没找到的话就显示未找到的提示

  • 同时还包括假如事件数组的大小不足的时候进行扩容的部分

    void Poller::poll(ChannelList &activechannellist)
    {
    int timeout = TIMEOUT;
    //std::cout << "epoll_wait..." << std::endl;(int)eventlist_.capacity()
    int nfds = epoll_wait(pollfd_, &*eventlist_.begin(), (int)eventlist_.capacity(), timeout);
    //int nfds = epoll_wait(pollfd_, &*eventlist_.begin(), (int)channelmap_.size()*0.7+1, timeout);
    if(nfds == -1)
    {
    printf("error code is:%d", errno);
    perror("epoll wait error");
    //exit(1);
    }
    //printf("event num:%d\n", nfds);
    //std::cout << "event num:" << nfds << "\n";// << std::endl;
    for(int i = 0; i < nfds; ++i)
    {
    int events = eventlist_[i].events;
    //int fd = eventlist_[i].data.fd;
    Channel *pchannel = (Channel*)eventlist_[i].data.ptr;
    int fd = pchannel->GetFd();
    if(channelmap_.find(fd) != channelmap_.end())
    {
    pchannel->SetEvents(events);
    activechannellist.push_back(pchannel);
    }
    else
    {
    std::cout << "not find channel!" << std::endl;
    }
    }
    if(nfds == (int)eventlist_.capacity())
    {
    std::cout << "resize:" << nfds << std::endl;
    eventlist_.resize(nfds * 2);
    }
    //eventlist_.clear();

    }

    AddChannel(Channel *pchannel)、RemoveChannel(Channel *pchannel)、UpdateChannel(Channel *pchannel)

  • 三个函数大体的代码逻辑相同,都是利用传入的Channel参数获取文件描述符fd,然后将前文提到的映射的map中相应的对象做出修改。然后使用epoll_ctl修改epoll对其的监视。

    //添加事件
    void Poller::AddChannel(Channel *pchannel)
    {
    int fd = pchannel->GetFd();
    struct epoll_event ev;
    ev.events = pchannel->GetEvents();
    //data是联合体
    //ev.data.fd = fd;
    ev.data.ptr = pchannel;
    channelmap_[fd] = pchannel;

    if(epoll_ctl(pollfd_, EPOLL_CTL_ADD, fd, &ev) == -1)
    {
    perror("epoll add error");
    exit(1);
    }
    //std::cout << "addchannel!" << std::endl;
    }

    //删除事件
    void Poller::RemoveChannel(Channel *pchannel)
    {
    int fd = pchannel->GetFd();
    struct epoll_event ev;
    ev.events = pchannel->GetEvents();
    ///ev.data.fd = fd;
    ev.data.ptr = pchannel;
    channelmap_.erase(fd);

    if(epoll_ctl(pollfd_, EPOLL_CTL_DEL, fd, &ev) == -1)
    {
    perror("epoll del error");
    exit(1);
    }
    //std::cout << "removechannel!" << std::endl;
    }

    //更新事件
    void Poller::UpdateChannel(Channel *pchannel)
    {
    int fd = pchannel->GetFd();
    struct epoll_event ev;
    ev.events = pchannel->GetEvents();
    //ev.data.fd = fd;
    ev.data.ptr = pchannel;

    if(epoll_ctl(pollfd_, EPOLL_CTL_MOD, fd, &ev) == -1)
    {
    perror("epoll update error");
    exit(1);
    }
    //std::cout << "updatechannel!" << std::endl;
    }

    epoll结构体的定义

    typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
    } epoll_data_t;

    struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
    };
  • events的定义

事件宏 定义
EPOLLIN 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT 表示对应的文件描述符可以写
EPOLLPRI 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

EventLoop

  • 这个类的主要作用是维护一个处理网络事件的循环,将Channelactivechannellist中取出,然后处理事件。

    构造函数 EventLoop(/* args */)

  • 构造vector<Functor> functorlist_,其中functorfunction<void()> Functor,同时还构造两个vector<Channel*> ChannelList的数组对象,再构造一个Poller对象,然后将自身的bool标记置为true。

    EventLoop::EventLoop(/* args */)
    : functorlist_(),
    channellist_(),
    activechannellist_(),
    poller_(),
    quit(true)
    {

    }

    循环函数 EventLoop::loop()

  • 主要作用是循环调用前面的epoll类的poll函数,然后从activechannellist中遍历处理每个的事件。然后清空activechannellist,并且执行函数列表里的函数完成任务。

    void EventLoop::loop()
    {
    quit = false;
    while(!quit)
    {
    poller_.poll(activechannellist_);
    //std::cout << "server HandleEvent" << std::endl;
    for(Channel *pchannel : activechannellist_)
    {
    pchannel->HandleEvent();//处理事件
    }
    activechannellist_.clear();
    ExecuteTask();
    }
    }

TcpServer

构造函数 TcpServer(EventLoop* loop, int port)

  • 利用初始化列表初始化SocketServersocket对象,初始化循环体loop对象,初始化Channel类型的serverchannel对象,初始化链接计数conncount对象
  • serversocket分别进行SetReuseAddr()BindAddress(port), Listen(), Setnonblocking()
  • serverchannel_初始化并且设置ReadHandleErrorHandle
    TcpServer::TcpServer(EventLoop* loop, int port)
    : serversocket_(),
    loop_(loop),
    serverchannel_(),
    conncount_(0)
    {
    //serversocket_.SetSocketOption();
    serversocket_.SetReuseAddr();
    serversocket_.BindAddress(port);
    serversocket_.Listen();
    serversocket_.Setnonblocking();

    serverchannel_.SetFd(serversocket_.fd());
    serverchannel_.SetReadHandle(std::bind(&TcpServer::OnNewConnection, this));
    serverchannel_.SetErrorHandle(std::bind(&TcpServer::OnConnectionError, this));

    }

    Start()函数

  • 设置channel的events,同时将channel添加到循环loop中
    void TcpServer::Start()
    {
    serverchannel_.SetEvents(EPOLLIN | EPOLLET);
    loop_->AddChannelToPoller(&serverchannel_);
    }

建立新连接的处理函数

  • 主要功能是接受客户端的连接请求,然后利用接收到的文件描述符创建新的控制链接的成员对象,注册业务函数并且完成调用。
    //新TCP连接处理,核心功能,业务功能注册,任务分发
    void TcpServer::OnNewConnection()
    {
    //循环调用accept,获取所有的建立好连接的客户端fd
    struct sockaddr_in clientaddr;
    int clientfd;
    while( (clientfd = serversocket_.Accept(clientaddr)) > 0)
    {
    //std::cout << "New client from IP:" << inet_ntoa(clientaddr.sin_addr)
    // << ":" << ntohs(clientaddr.sin_port) << std::endl;

    if(++conncount_ >= MAXCONNECTION)
    {
    close(clientfd);
    continue;
    }
    Setnonblocking(clientfd);

    //创建连接,注册业务函数
    TcpConnection *ptcpconnection = new TcpConnection(loop_, clientfd, clientaddr);
    ptcpconnection->SetMessaeCallback(messagecallback_);
    ptcpconnection->SetSendCompleteCallback(sendcompletecallback_);
    ptcpconnection->SetCloseCallback(closecallback_);
    ptcpconnection->SetErrorCallback(errorcallback_);
    ptcpconnection->SetConnectionCleanUp(std::bind(&TcpServer::RemoveConnection, this, ptcpconnection));
    tcpconnlist_[clientfd] = ptcpconnection;

    newconnectioncallback_(ptcpconnection);
    }
    }

RemoveConnection(TcpConnection *ptcpconnection)

  • 连接断开的操作
    //连接清理
    void TcpServer::RemoveConnection(TcpConnection *ptcpconnection)
    {
    --conncount_;
    //std::cout << "clean up connection, conncount is" << conncount_ << std::endl;
    tcpconnlist_.erase(ptcpconnection->fd());
    delete ptcpconnection;
    }

OnConnectionError()

void TcpServer::OnConnectionError()
{
std::cout << "UNKNOWN EVENT" << std::endl;
serversocket_.Close();
}

Setnonblocking(int fd)

void Setnonblocking(int fd)
{
int opts = fcntl(fd, F_GETFL);
if (opts < 0)
{
perror("fcntl(fd,GETFL)");
exit(1);
}
if (fcntl(fd, F_SETFL, opts | O_NONBLOCK) < 0)
{
perror("fcntl(fd,SETFL,opts)");
exit(1);
}
}

分析基于epoll的C++高性能webServer代码(二)

C++bind()函数用法

  • 参考链接

  • std::bind()函数作为函数的适配器,它可以扩大函数是使用场合,使得函数更加灵活的被使用。
    template<class F, class… Args>

bind(F&&f, Args&&… args);
  • 参数:
    f 可以是function object,函数指针,函数引用,成员函数指针,或者数据成员的指针。
  • 返回值:
    function object

可以用std::placeholders::_1等替换函数本身的输入参数

  • 举例:
#include <iostream>
#include <functional>
using namespace std;

using namespace std::placeholders;

void func(int a, int b, int c)
{
cout << (a -b -c) << endl;
}

int main(int argc, char *argv[])
{
auto fn1 = bind(func, _1, 2, 3);
auto fn2 = bind(func, 2, _1, 3);

fn1(10);
fn2(10);
return 0;
}

/*
输出
5
-11
*/
  • 注意上面的三输入函数func使用bind替换掉了其中的两个输入参数,仅仅在placeholder的位置提供一个输入
  • 注意,当具有多个输入的时候,输入的顺序是按照std::placeholders::_1std::placeholders::_2std::placeholders::_3等的顺序
  • 举例:
#include <iostream>
#include <functional>

using namespace std;
using namespace std::placeholders;

void func(int a, int b, int c)
{
cout << (a - b -c) << endl;
}

int main(int argc, char *argv[])
{
auto fn1= bind(func, _2, 2, _1);
cout << "the value of function is :";
fn1(1, 13);

auto fn2 = bind(func, _1, 2, _2);
cout << "the value of function after changing placeholder position is :";
fn2(1, 13);
return 0;
}

/*
输出
the value of function is :10
the value of function after changing placeholder position is :-14
*/
  • 注意上面的函数中,fn1的实际输入到func中的参数是func(13, 2, 1),而输入到fn2中的参数实际上是func(1, 2, 13)

httpServer的成员函数分析

构造函数

  • 使用初始化列表初始化成员tcpserver_,然后将tcpserver_的函数接口全部初始化为HttpServer提供的处理函数
HttpServer::HttpServer(EventLoop *loop, int port)
: tcpserver_(loop, port),
cnt(0)
{
tcpserver_.SetNewConnCallback(std::bind(&HttpServer::HandleNewConnection, this, std::placeholders::_1));
tcpserver_.SetMessageCallback(std::bind(&HttpServer::HandleMessage, this, std::placeholders::_1, std::placeholders::_2));
tcpserver_.SetSendCompleteCallback(std::bind(&HttpServer::HandleSendComplete, this, std::placeholders::_1));
tcpserver_.SetCloseCallback(std::bind(&HttpServer::HandleClose, this, std::placeholders::_1));
tcpserver_.SetErrorCallback(std::bind(&HttpServer::HandleError, this, std::placeholders::_1));
}

HandleNewConnection(TcpConnection *ptcpconn)

  • 处理新建立的http连接请求
  • 新建一个HttpSession对象
  • 将该对象放入httpsessionlist_中(httpsessionlist_是一个map,通过TcpConnection映射到httpSession对象)
void HttpServer::HandleNewConnection(TcpConnection *ptcpconn)
{
//std::string msg(s);
HttpSession *phttpsession = new HttpSession();
httpsessionnlist_[ptcpconn] = phttpsession;
}

HandleMessage(TcpConnection *ptcpconn, std::string &s)

  • 调用httpsessionlist_中的对象处理信息
  • 将同样的数据发送回发送方(回显)
  • 处理短链接问题
void HttpServer::HandleMessage(TcpConnection *ptcpconn, std::string &s)
{
//std::cout << "http num is:" << ++cnt << std::endl;
HttpSession *phttpsession = httpsessionnlist_[ptcpconn];
phttpsession->PraseHttpRequest(s);
phttpsession->HttpProcess();
std::string msg;
phttpsession->AddToBuf(msg);
ptcpconn->Send(msg);
if(!phttpsession->KeepAlive())
{
//短连接,可以告诉框架层数据发完就可以关掉TCP连接,不过这里注释掉,还是交给客户端主动关闭吧
//ptcpconn->HandleClose();
}
}
  • HttpProcess()的行为:
void HttpSession::HttpProcess()
{
if("GET" == httprequestcontext_.method)
{
;
}
else if("POST" == httprequestcontext_.method)
{
;
}
else
{
std::cout << "HttpServer::HttpParser" << std::endl;
errormsg = "Method Not Implemented";
HttpError(501, "Method Not Implemented");
}

size_t pos = httprequestcontext_.url.find("?");
if(pos != std::string::npos)
{
path_ = httprequestcontext_.url.substr(0, pos);
querystring_ = httprequestcontext_.url.substr(pos+1);
}
else
{
path_ = httprequestcontext_.url;
}

//keepalive判断处理
std::map<std::string, std::string>::const_iterator iter = httprequestcontext_.header.find("Connection");
if(iter != httprequestcontext_.header.end())
{
keepalive_ = (iter->second == "Keep-Alive");
}
else
{
if(httprequestcontext_.version == "HTTP/1.1")
{
keepalive_ = true;//HTTP/1.1默认长连接
}
else
{
keepalive_ = false;//HTTP/1.0默认短连接
}
}

responsebody_.clear();
if("/" == path_)
{
path_ = "/index.html";
}
else if("/hello" == path_)
{
//Wenbbench 测试用
std::string filetype("text/html");
responsebody_ = ("hello world");
responsecontext_ += httprequestcontext_.version + " 200 OK\r\n";
responsecontext_ += "Server: Chen Shuaihao's NetServer/0.1\r\n";
responsecontext_ += "Content-Type: " + filetype + "; charset=utf-8\r\n";
if(iter != httprequestcontext_.header.end())
{
responsecontext_ += "Connection: " + iter->second + "\r\n";
}
responsecontext_ += "Content-Length: " + std::to_string(responsebody_.size()) + "\r\n";
responsecontext_ += "\r\n";
responsecontext_ += responsebody_;
return;
}
else
{
;
}

//std::string responsebody;
path_.insert(0,".");
FILE* fp = NULL;
if((fp = fopen(path_.c_str(), "rb")) == NULL)
{
//perror("error fopen");
//404 NOT FOUND
HttpError(404, "Not Found");
return;
}
else
{
char buffer[4096];
memset(buffer, 0, sizeof(buffer));
while(fread(buffer, sizeof(buffer), 1, fp) == 1)
{
responsebody_.append(buffer);
memset(buffer, 0, sizeof(buffer));
}
if(feof(fp))
{
responsebody_.append(buffer);
}
else
{
std::cout << "error fread" << std::endl;
}
fclose(fp);
}

std::string filetype("text/html"); //暂时固定为html
responsecontext_ += httprequestcontext_.version + " 200 OK\r\n";
responsecontext_ += "Server: Chen Shuaihao's NetServer/0.1\r\n";
responsecontext_ += "Content-Type: " + filetype + "; charset=utf-8\r\n";
if(iter != httprequestcontext_.header.end())
{
responsecontext_ += "Connection: " + iter->second + "\r\n";
}
responsecontext_ += "Content-Length: " + std::to_string(responsebody_.size()) + "\r\n";
responsecontext_ += "\r\n";
responsecontext_ += responsebody_;
}

HttpServer::HandleClose(TcpConnection *ptcpconn)

  • 通过传入的参数TcpConnecton和map映射找到对应的httpSession
  • httpSession从列表中删除
  • 释放httpSession占用的内存空间
void HttpServer::HandleClose(TcpConnection *ptcpconn)
{
HttpSession *phttpsession = httpsessionnlist_[ptcpconn];
httpsessionnlist_.erase(ptcpconn);
delete phttpsession;
}

HttpServer::HandleError(TcpConnection *ptcpconn)

  • 删除对应的httpSession同上
void HttpServer::HandleError(TcpConnection *ptcpconn)
{
HttpSession *phttpsession = httpsessionnlist_[ptcpconn];
httpsessionnlist_.erase(ptcpconn);
delete phttpsession;
}

HttpServer::Start()

  • 直接调用内部的tcpserver成员的start函数
void HttpServer::Start()
{
tcpserver_.Start();
}

分析基于epoll的C++高性能webServer代码(一)

std::function用法

  • 参考链接 cpp官方reference
  • 类模板std :: function是一个通用的多态函数包装器std :: function的实例可以存储,复制和调用任何可调用的目标 :包括函数,lambda表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。当std::function对象未包裹任何实际的可调用元素,调用该std::function对象将抛出std::bad_function_call异常。

成员函数

成员函数声明 说明
constructor 构造函数:constructs a new std::function instance
destructor 析构函数: destroys a std::function instance
operator= 给定义的function对象赋值
operator bool 检查定义的function对象是否包含一个有效的对象
operator() 调用一个对象

使用例

  • 调用普通函数
#include <functional>
#include <iostream>
int f(int a, int b)
{
return a+b;
}
int main()
{
std::function<int(int, int)>func = f;
cout<<func(1, 2)<<endl; // 3
system("pause");
return 0;
}

  • 调用模板函数对象
#include<iostream>
using namespace std;

//function object
template<class T>
struct functor
{
public:
T operator() (T a, T b)
{
return a + b;
}
};
int main()
{
functor ft;
function<int(int,int)> func = ft<int>();
cout<<func(1,2)<<endl; //3
return 0;
}
  • 调用lambda表达式
#include <functional>
#include <iostream>
using namespace std;
int main()
{
auto f = [](const int a, const int b) {return a + b; };
std::function<int(int, int)>func = f;
cout << func(1, 2) << endl; // 3
system("pause");
return 0;
}

epoll原理以及讲解

pytorch训练的模型导出网格数据或者部署到C++平台

数据导出

  • 导出为.csv文件
  • 参考教程
# 定义和实例化网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1,30,kernel_size=5)
self.fc1 = nn.Linear(4320,100)
self.fc2 = nn.Linear(100, 10)
def forward(self,x):
x = F.max_pool2d(F.relu(self.conv1(x)),2)
x = x.view(-1, 4320)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x,dim=1)
network = Net()

# 保存.csv格式的参数
for name,param in network.named_parameters():
print(f"name:{name}\t\t\t,shape:{param.shape}")
data = pd.DataFrame(param.detach().numpy().reshape(1, -1))
filename = f"{name}.csv"
data.to_csv(f"./{filename}", index=False, header=False, sep=',')

使用Libtorch直接导出C++模型

python多线程以及线程同步

多线程基础知识

  • python创建多线程主要依靠threading库
import threading
  • 线程的创建使用Thread()函数,提供的参数为线程的函数指针和函数的参数(必须为可迭代的对象)
def task(num):
for i in range(num):
print(i)

t1=threading.Thread(target=task, args=(3,)) # 注意此处不能使用args=(3),否则会因为传入的参数不可迭代导致错误,必须加逗号
  • 线程的开始使用start(),使用join()回收线程
# 多线程
t1.start()
t2.start()
t3.start()

# 回收线程
t1.join()
t2.join()
t3.join()

多线程时间测试

  • 线程执行任务为控制台输出的
import threading
import time

def task(num):
for i in range(num):
print(i)

t1=threading.Thread(target=task, args=(3,))
t2=threading.Thread(target=task, args=(3,))
t3=threading.Thread(target=task, args=(3,))

start = time.time()

# 多线程

t1.start()
t2.start()
t3.start()

# 回收线程
t1.join()
t2.join()
t3.join()

print("多线程用时:%.8f\n"%(time.time()-start))

# 单线程

start = time.time()

for i in range(3):
task(3)

end = time.time()

print("单线程用时:%.8f\n"%(end-start))
  • image-20220628093137346

    • 可见,多线程用时还更多一些
  • 多线程执行不涉及IO的操作(比如加法)

import threading
import time

def task(num):
count = 0
for i in range(num):
count+=1

t1=threading.Thread(target=task, args=(3,))
t2=threading.Thread(target=task, args=(3,))
t3=threading.Thread(target=task, args=(3,))

start = time.time()

# 多线程

t1.start()
t2.start()
t3.start()

# 回收线程
t1.join()
t2.join()
t3.join()

print("多线程用时:%.8f\n"%(time.time()-start))

# 单线程

start = time.time()

for i in range(3):
task(3)

end = time.time()

print("单线程用时:%.8f\n"%(end-start))
  • 输出结果为
    • image-20220628093356210
    • 可见,单线程比多线程的优势更明显了

原因

  • 你会发现多线程比单线程花费的时间还要更多,这是因为GIL的原因。

    GIL的全称是Global Interpreter Lock(全局解释器锁),Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只能由一个线程在解释器中运行。因此Python中的多线程是表面上的多线程(同一时刻只有一个线程),不是真正的多线程。

    但是如果是因为GIL的原因,就说多线程无用是不对的,对于IO密集的程序,多线程是要比单线程快的。

  • 参考链接

python线程同步

  • python线程同步具有一些与C类似的机制,比如各种锁和获取、释放锁等等
  • 参考链接

同步锁Lock

lock = threading.Lock()
lock.acquire()
lock.release()
  • 用于保护临界区

  • 由于threading.Lock()对象中实现了enter__()与__exit()方法,故我们可以使用with语句进行上下文管理形式的加锁解锁操作

with lock:
# 自动加锁
global num
for i in range(10_000_000):
num += 1
# 自动解锁

RLock() 递归锁

  • 递归锁是同步锁的一个升级版本,在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。

  • 其他部分类似,不再详细赘述

Condition() 条件锁

  • 条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()与notify()来控制线程执行的个数。

    注意:条件锁可以自由设定一次放行几个线程。

  • img

import threading

currentRunThreadNumber = 0
maxSubThreadNumber = 10


def task():
global currentRunThreadNumber
thName = threading.currentThread().name

condLock.acquire() # 上锁
print("start and wait run thread : %s" % thName)

condLock.wait() # 暂停线程运行、等待唤醒
currentRunThreadNumber += 1
print("carry on run thread : %s" % thName)

condLock.release() # 解锁


if __name__ == "__main__":
condLock = threading.Condition()

for i in range(maxSubThreadNumber):
subThreadIns = threading.Thread(target=task)
subThreadIns.start()

while currentRunThreadNumber < maxSubThreadNumber:
notifyNumber = int(
input("Please enter the number of threads that need to be notified to run:"))

condLock.acquire()
condLock.notify(notifyNumber) # 放行
condLock.release()

print("main thread run end")

# 先启动10个子线程,然后这些子线程会全部变为等待状态
# start and wait run thread : Thread-1
# start and wait run thread : Thread-2
# start and wait run thread : Thread-3
# start and wait run thread : Thread-4
# start and wait run thread : Thread-5
# start and wait run thread : Thread-6
# start and wait run thread : Thread-7
# start and wait run thread : Thread-8
# start and wait run thread : Thread-9
# start and wait run thread : Thread-10

# 批量发送通知,放行特定数量的子线程继续运行
# Please enter the number of threads that need to be notified to run:5 # 放行5个
# carry on run thread : Thread-4
# carry on run thread : Thread-3
# carry on run thread : Thread-1
# carry on run thread : Thread-2
# carry on run thread : Thread-5

# Please enter the number of threads that need to be notified to run:5 # 放行5个
# carry on run thread : Thread-8
# carry on run thread : Thread-10
# carry on run thread : Thread-6
# carry on run thread : Thread-9
# carry on run thread : Thread-7

# Please enter the number of threads that need to be notified to run:1
# main thread run end
  • 注意,上面的代码中的线程在使用acquire()获取锁之后,进入了休眠状态(也就是wait()),然后等待锁的notify()函数唤醒相应数量的正在休眠的进程

Event() 事件锁

  • 事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。

    我们可以将事件锁看为红绿灯,当红灯时所有子线程都暂停运行,并进入“等待”状态,当绿灯时所有子线程都恢复“运行”。

  • img

import threading
maxSubThreadNumber = 3
def task():
thName = threading.currentThread().name
print("start and wait run thread : %s" % thName)
eventLock.wait() # 暂停运行,等待绿灯
print("green light, %s carry on run" % thName)
print("red light, %s stop run" % thName)
eventLock.wait() # 暂停运行,等待绿灯
print("green light, %s carry on run" % thName)
print("sub thread %s run end" % thName)

if __name__ == "__main__":
eventLock = threading.Event()
for i in range(maxSubThreadNumber):
subThreadIns = threading.Thread(target=task)
subThreadIns.start()
eventLock.set() # 设置为绿灯
eventLock.clear() # 设置为红灯
eventLock.set() # 设置为绿灯

# start and wait run thread : Thread-1
# start and wait run thread : Thread-2
# start and wait run thread : Thread-3
# green light, Thread-1 carry on run
# red light, Thread-1 stop run
# green light, Thread-1 carry on run
# sub thread Thread-1 run end
# green light, Thread-3 carry on run
# red light, Thread-3 stop run
# green light, Thread-3 carry on run
# sub thread Thread-3 run end
# green light, Thread-2 carry on run
# red light, Thread-2 stop run
# green light, Thread-2 carry on run
# sub thread Thread-2 run end
  • 不能使用with语句进行调用

Semaphore() 信号量锁

  • 信号量锁也是根据条件锁来做的,它与条件锁和事件锁的区别如下:

    • 条件锁:一次可以放行任意个处于“等待”状态的线程
    • 事件锁:一次可以放行全部的处于“等待”状态的线程
    • 信号量锁:通过规定,成批的放行特定个处于“上锁”状态的线程
  • img

import threading
import time

maxSubThreadNumber = 6


def task():
thName = threading.currentThread().name
semaLock.acquire()
print("run sub thread %s" % thName)
time.sleep(3)
semaLock.release()


if __name__ == "__main__":
# 每次只能放行2个
semaLock = threading.Semaphore(2)

for i in range(maxSubThreadNumber):
subThreadIns = threading.Thread(target=task)
subThreadIns.start()


# run sub thread Thread-1
# run sub thread Thread-2

# run sub thread Thread-3
# run sub thread Thread-4

# run sub thread Thread-6
# run sub thread Thread-5
  • 注意调用Semaphore()初始化的时候传递参数指定同时能够放行的线程数量
  • 也可以使用with语句
import threading
import time

maxSubThreadNumber = 6


def task():
thName = threading.currentThread().name
with semaLock:
print("run sub thread %s" % thName)
time.sleep(3)


if __name__ == "__main__":

semaLock = threading.Semaphore(2)

for i in range(maxSubThreadNumber):
subThreadIns = threading.Thread(target=task)
subThreadIns.start()

WSL2上使用matplotlib结合VSCode远程不显示输出的解决方法

报错信息

问题一般分为两种

  • 没有报错但是使用plt.show()之后没有任何输出
  • 使用matplotlib.use('Tkagg')之后出现报错Cannot load backend 'TkAgg' which requires the 'tk' interactive framework, as 'headless' is currently running,或者是_tkinter.TclError: couldn't connect to display ":0.0"
    • image-20220627132430453

解决方法

  • 开始Import的时候改变顺序
import matplotlib
matplotlib.use('Tkagg') #这一句修改使用的后端输出设备
import matplotlib.pyplot as plt
  • 然后在windows上安装VcXsrv服务器并且启动,具体下载地址为 sourceForge

  • 安装过程中一路选择默认设置

  • 然后通过桌面图标启动VcXsrv

  • preview

  • preview

  • preview

  • preview

  • 然后正常情况会弹出Windows防火墙提示,允许访问即可

  • 然后修改WSL端的环境

  • 首先需要查看Windows系统和WSL2通信使用的虚拟网卡地址

sudo vim /etc/resolv.conf

# nameserver后面的地址就是Windows系统虚拟网卡的地址,记一下
# 需要取消下面两行内容的注释,禁用自动重新生成配置文件,否则重启后这个地址会变
[network]
generateResolvConf = false
  • 然后修改.bashrc
$ vim ~/.bashrc
# 在文件最后追加下面内容,地址使用上面查看到的
export DISPLAY=<nameServer IP地址>:0

$ source ~/.bashrc

详细可以参考配置VcXsrv

  • 最终效果
  • image-20220627133421950

C语言使用并行执行的for循环

头文件

#include <omp.h>

编译指令

g++ test111.cpp -o paraCpp111 -fopenmp
  • 注意要添加-fopenmp选项

设置线程数量

#pragma omp parallel
omp_set_num_threads(2); //参数为线程的数量

使用parallel的for循环

#pragma omp parallel for
for(int i = 0;i<max;++i)
{
printf("%d\n", i);
}
  • 注意#pragma omp parallel for这句必须和for循环紧邻,中间不能有其他语句,否则会出错

执行效果

不进行线程设置和线程设置为2的效果对比:

    printf("1线程\n");
before = clock();

for(int i = 0;i<max;++i)
{
printf("%d\n", i);
}
timePeriod = clock()-before;
cout<<timePeriod<<'!'<<endl;
printf("2线程\n");
// 此处设置了2线程
#pragma omp parallel
omp_set_num_threads(2);

before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
printf("%d\n", i);
}
timePeriod = clock()-before;
cout<<timePeriod<<'!'<<endl;

输出对比:

  • image-20220622153605823

  • 注意到没有开启并行for循环的输出是按照顺序的,开启的输出是乱序的

  • 同时经过尝试发现,假如开启了并行for循环的输出但是指定线程为1的,输出仍然是乱序的,暂时还不清楚原因

对比大量计算和多组计算:

#include <iostream>
#include <omp.h>
#include <ctime>
#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <stdlib.h>
using namespace std;
int main()
{
int max = 1e8;
int before, timePeriod;
int array1[100];
int array2[100];
int array4[100];
int array8[100];
for(int count = 0;count<100;++count)
{
#pragma omp parallel
omp_set_num_threads(1);
// printf("1线程\n");
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("2线程\n");
array1[count] = timePeriod;
#pragma omp parallel
omp_set_num_threads(2);
// printf("2线程\n");
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("4线程\n");
array2[count] = timePeriod;
#pragma omp parallel

omp_set_num_threads(4);
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("8线程\n");
array4[count] = timePeriod;
#pragma omp parallel

omp_set_num_threads(8);
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock() - before;
// cout<<timePeriod<<'!'<<endl;
array8[count] = timePeriod;
}

ofstream out;
out.open("data.csv", ios::out);

for(int cnt = 0;cnt<100;++cnt)
{
out<<array1[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array2[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array4[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array8[cnt]<<',';
}
out<<endl;

out.close();
system("python3 showData.py");
}

  • 其中分别开启1、2、4、8线程,进行for循环的计算,对比所需时间(通过clock()进行测量),最后调用命令行脚本system,开启另外撰写的python脚本进行数据折线图的显示

  • image-20220622154102661

    • 上图是分别开启1、2、4、8线程的计算结果
  • 对于代码进行简单修改,在1线程的时候取消对于parallel的使用,实验结果如下图

  • image-20220622154349763

    • 可见结果在1线程的时候,某些情况下会占用很长的时间
#include <iostream>
#include <omp.h>
#include <ctime>
#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <stdlib.h>
using namespace std;
int main()
{
int max = 1e8;
int before, timePeriod;
int array1[100];
int array2[100];
int array4[100];
int array8[100];
for(int count = 0;count<100;++count)
{
// #pragma omp parallel
// omp_set_num_threads(1);
// printf("1线程\n");
before = clock();
// #pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("2线程\n");
array1[count] = timePeriod;
#pragma omp parallel
omp_set_num_threads(2);
// printf("2线程\n");
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("4线程\n");
array2[count] = timePeriod;
#pragma omp parallel

omp_set_num_threads(4);
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock()-before;
// cout<<timePeriod<<'!'<<endl;
// printf("8线程\n");
array4[count] = timePeriod;
#pragma omp parallel

omp_set_num_threads(8);
before = clock();
#pragma omp parallel for
for(int i = 0;i<max;++i)
{
// cout<<i<<endl;
}
timePeriod = clock() - before;
// cout<<timePeriod<<'!'<<endl;
array8[count] = timePeriod;
}

ofstream out;
out.open("data.csv", ios::out);

for(int cnt = 0;cnt<100;++cnt)
{
out<<array1[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array2[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array4[cnt]<<',';
}
out<<endl;

for(int cnt = 0;cnt<100;++cnt)
{
out<<array8[cnt]<<',';
}
out<<endl;

out.close();
system("python3 showData.py");
}

wsl2的Python以及增加远程用交互界面

wsl2的pip相关问题以及安装问题

  • wsl2(Ubuntu20.04)有时候会出现一些软件包安装不上的问题,或者是安装某些包的时候出现Python.h无法编译,建议安装python3-dev等情况,此时并不需要安装上面提示安装的内容,而是系统自身的python出现了问题。此时只需要通过以下的命令彻底卸载Python然后再重新安装python3和pip即可
sudop apt-get remove python3-pip
sudo apt-get remove python3.8 #或者其他版本
sudo apt-get purge python3
  • 直到在命令行输入python3不再自动进入python互动命令行模式即可

  • 此外,假如安装的时候持续出现安装问题,比如依赖包不能解决的问题,可能是软件源的问题。此时建议使用清华源,也就是

https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse

# 预发布软件源,不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse

WSL的Ubuntu20.04配置远程桌面

在完成上述的换源等步骤之后,开始为Ubuntu20.04配置远程桌面。参考这篇WSL2 Ubuntu图形界面安装与远程桌面在Windows 10 WSL2中使用图形界面(GUI)

  • 安装桌面环境
sudo apt install ubuntu-desktop
#或者

#KDE
sudo apt install kubuntu-desktop

#Xfce
sudo apt install xubuntu-desktop
  • 安装xrdp
sudo apt purge xrdp
sudo apt install -y xrdp
  • 非必须)更改XRDP的一些配置:增加bpp(bits per pixel),让远程连接质量更好
sudo sed -i 's/max_bpp=32/#max_bpp=32\nmax_bpp=128/g' /etc/xrdp/xrdp.ini
sudo sed -i 's/xserverbpp=24/#xserverbpp=24\nxserverbpp=128/g' /etc/xrdp/xrdp.ini
echo xfce4-session > ~/.xsession
  • 接下来更改XRDP的启动脚本,让它同时启动Xfce
sudo vim /etc/xrdp/startwm.sh
  • 把文件的最后几行改成这样:
# test -x /etc/X11/Xsession && exec /etc/X11/Xsession
# exec /bin/sh /etc/X11/Xsession
# xfce
startxfce4
  • 启动xrdp
sudo /etc/init.d/xrdp start
  • 默认端口为3389
  • 在wsl上安装net-tools,然后使用ifconfig命令查看wsl 的IP地址,然后使用Windows远程桌面链接即可
  • img

使用网页版的触控板遥控电脑

基于flask和pyautogui开发的后端

from flask import Flask
from flask import request
from flask import make_response
from flask import send_from_directory
import json
import os

import pyautogui

app = Flask(__name__)


@app.route('/')
def returnIndex():
return send_from_directory('', 'remotePage.html')


@app.route('/mousemove', methods=['POST', 'GET'])
def moveMouse():
data = json.loads(request.get_data())
xMove = data['x']
yMove = data['y']
pyautogui.moveRel(xOffset=xMove, yOffset=yMove, duration=0.0, tween=pyautogui.linear)
return json.dumps(" ")


@app.route('/click', methods=['POST', 'GET'])
def click():
data = json.loads(request.get_data())
clickTime = data['time']
clickBtn = data['btn']
if (clickTime == 1):
if (clickBtn == 1): # 左单击
pyautogui.click(x=None, y=None, clicks=1, interval=0.0, button='left', duration=0.0, tween=pyautogui.linear)
else: # 右单击
pyautogui.click(x=None, y=None, clicks=1, interval=0.0, button='right', duration=0.0,
tween=pyautogui.linear)
else:
if (clickBtn == 1): # 左双击
pyautogui.doubleClick(x=None, y=None, interval=0.0, button='left', duration=0.0,
tween=pyautogui.linear)
else: # 右双击
pyautogui.doubleClick(x=None, y=None, interval=0.0, button='right', duration=0.0,
tween=pyautogui.linear)
return json.dumps(" ")


@app.route('/key', methods=['POST', 'GET'])
def keyBoard():
data = json.loads(request.get_data())
key = data['key']
moveType = data['move']
if (moveType == 1):
pyautogui.keyDown(key)
else:
pyautogui.keyUp(key)
return json.dumps(" ")

@app.route('/altTabAndWin' ,methods = ['POST','GET'])
def altAndWin():
data = json.loads(request.get_data())
key = data['key']
if(key == 'altTab'):
pyautogui.hotkey('altleft', 'tab')
else:
pyautogui.press('winleft')
return json.dumps(" ")

if __name__ == '__main__':
# app.config['JSON_AS_ASCII'] = False
app.run(host='0.0.0.0', port=8080)

前端html

<!DOCTYPE html>
<html>
<head>
<script src = "https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<!-- <div id = "div1">0, 0</div> -->
<!-- <button id="Mup" style="width: 10%; height: 30px; left: 45%;top:10px;position:absolute" onmousedown="handleMDown(event)"onmouseup = "handleMUp(event)">↑</button>
<button id="Mleft" style="width: 10%; height: 30px; left: 20%;top:60px;position:absolute" onmousedown="handleMDown(event)"onmouseup = "handleMUp(event)">←</button>
<button id="Mright" style="width: 10%; height: 30px; left: 70%;top: 60px;position:absolute" onmousedown="handleMDown(event)"onmouseup = "handleMUp(event)">→</button>
<button id="Mdown" style="width: 10%; height: 30px; left: 45%;top:110px;position:absolute" onmousedown="handleMDown(event)"onmouseup = "handleMUp(event)">↓</button> -->
<button id = "click" style = "width: 12%; height: 30px; left: 20%;top:160px;position:absolute"onclick = "handleClick(event);">左击</button>
<button id = "dClick" style = "width: 12%; height: 30px; left: 44%;top:160px;position:absolute"onclick = "handleClick(event);">双击</button>
<button id = "rClick" style = "width: 12%; height: 30px; left: 68%;top:160px;position:absolute"onclick = "handleClick(event);">右击</button>
<hr style="top:190px;position:relative">
<button id = "esc" style = "width: 10%; height: 30px; left: 20%;top:200px;position:absolute" onmousedown="handleDown(event)" onmouseup="handleUp(event)">Esc</button>
<button id = " " style = "width: 30%; height: 30px; left: 35%;top:200px;position:absolute" onmousedown="handleDown(event)" onmouseup="handleUp(event)">Space</button>
<button id = "enter" style = "width: 10%; height: 30px; left: 70%;top:200px;position:absolute"onmousedown="handleDown(event)" onmouseup="handleUp(event)">Enter</button>
<button id = "left" style = "width: 10%; height: 30px; left: 20%;top:240px;position:absolute"onmousedown="handleDown(event)"onmouseup="handleUp(event)">l</button>
<button id = "up" style = "width: 10%; height: 30px; left: 35%;top:240px;position:absolute"onmousedown="handleDown(event)"onmouseup="handleUp(event)">u</button>
<button id = "down" style = "width: 10%; height: 30px; left: 55%;top:240px;position:absolute"onmousedown="handleDown(event)"onmouseup="handleUp(event)">d</button>
<button id = "right" style = "width: 10%; height: 30px; left: 70%;top:240px;position:absolute"onmousedown="handleDown(event)"onmouseup="handleUp(event)">r</button>
<button id = "altTab" style = "width: 10%; height: 30px; left: 20%;top:280px;position:absolute"onclick = "handleClick(event);">AltTab</button>
<button id = "Win" style = "width: 10%; height: 30px; left: 70%;top:280px;position:absolute"onclick = "handleClick(event);">Win</button>
</body>

<script>

// mouseXPrev = window.event.clientX;
// mouseYPrev = window.touch.pageY;

mouseXPrev = -1;
mouseYPrev = -1;
// event.preventDefault();
document.addEventListener('touchstart', function(event)
{
event.preventDefault();
mouseXPrev = event.targetTouches[0].pageX;
mouseYPrev = event.targetTouches[0].pageY;
//document.getElementById("div1").value = mouseXPrev + ', ' + mouseYPrev;
})
document.addEventListener('touchmove', function(event)
{

var touch = event.targetTouches[0];
// if(mouseXPrev<0)mouseXPrev = Number(touch.pageX);
// if(mouseYPrev<0)mouseYPrev = Number(touch.pageY);
var dataToSend = {'x':0,'y':0};
if(touch.pageY>160)return;
var x = Number(touch.pageX)-mouseXPrev;
var y = Number(touch.pageY)-mouseYPrev;
dataToSend['x'] = x;
dataToSend['y'] = y;
mouseXPrev = Number(touch.pageX);
mouseYPrev = Number(touch.pageY);
var jsonToSend = JSON.stringify(dataToSend);
console.log(jsonToSend)
$.ajax({
type:"POST",
url:"mousemove",
data:jsonToSend,
success: function(datas)
{}
})
})


/*
function handleMDown(buttonClicked)
{
switch(buttonClicked.target.id)
{
case 'Mup':Mup = true;break;
case 'Mdown':Mdown = true;break;
case 'Mleft':Mleft = true;break;
case 'Mright':Mright = true;break;
}
}

function handleMUp(buttonClicked)
{
switch(buttonClicked.target.id)
{
case 'Mup':Mup = false;break;
case 'Mdown':Mdown = false;break;
case 'Mleft':Mleft = false;break;
case 'Mright':Mright = false;break;
}
}
*/

function handleDown(buttonClicked)
{
var dataToSend = {'key':buttonClicked.target.id,'move':1};
var jsonToSend = JSON.stringify(dataToSend);
//console.log(jsonToSend)
$.ajax({
type:"POST",
url:"key",
data:jsonToSend,
success: function(datas)
{}
})
}

function handleUp(buttonClicked)
{
var dataToSend = {'key':buttonClicked.target.id,'move':2};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"key",
data:jsonToSend,
success: function(datas)
{}
})
}

function handleClick(buttonClicked)
{
switch(buttonClicked.target.id)
{
case 'click':
var dataToSend = {'time':1,'btn':1};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"click",
data:jsonToSend,
success: function(datas)
{}
})
break;
case 'dClick':
var dataToSend = {'time':2,'btn':1};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"click",
data:jsonToSend,
success: function(datas)
{}
})
break;
case 'rClick':
var dataToSend = {'time':1,'btn':2};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"click",
data:jsonToSend,
success: function(datas)
{}
})
break;
case 'altTab':
var dataToSend = {'key':'altTab'};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"altTabAndWin",
data:jsonToSend,
success: function(datas)
{}
})
break;
case 'Win':
var dataToSend = {'key':'win'};
var jsonToSend = JSON.stringify(dataToSend);
// console.log(jsonToSend)
$.ajax({
type:"POST",
url:"altTabAndWin",
data:jsonToSend,
success: function(datas)
{}
})
break;
}
}


</script>


</html>

  • 前端的要点是调用了event.targetTouches这一属性,获得手指按压或者是触控笔的位置
  • 此API在计算机端无法使用