岩川黑鬼 发表于 2016-1-1 18:35:17

[C氵] 非抢占式并发(1):p7简介

本帖最后由 岩川黑鬼 于 2016-1-2 17:39 编辑

看了抚子的boost协程介绍,觉得自己搞那套玩意儿还是太污。不过也姑且安利一下。

一般来说,开发者习于抢占式的并发。在高级语言语句和/或表达式的层面上,抢占式并发很可能不保证任何串行行为,因为它们的重调度触发很可能来自某个外部定时器。在这一前提下,原子化、临界、工人和池是常见的手法,actor风格的并发也稍稍流行。然而如果我们回顾对并发这话题进行探讨的历史,会发现还有CSP(Communicating Sequential Processes, 通信顺序进程)这样的观念。对Go有所了解的读者或许会想到goroutine, 不过单纯的迫真并发并非CSP的全部。

翻开Hoare本人对CSP的论述,他强调过trace这一概念——也即,并发成分产生的确定轨迹。何处串行、何处并发,应当予以演算推定。

实现并没法做得很好,不过我仍然尝试在C中再现一点goroutine. 这结果是p7.
p7提供用户态的并发。实际的并发由可定义数目的pthread(后称线程)搭载,同一线程上的用户线程(粗糙期间,以后称作协程)在没有被迫使重调度的情况下顺序执行。重调度发生的时机可能有:

[*]用户主动暂时放弃;
[*]阻塞在瘦自旋锁或读写自旋锁上;
[*]阻塞在用户指定的IO上;
[*]阻塞在协程间通信上。
同时,若线程上执行了创建协程的代码,会触发一次主动放弃。

这不是个高效的方案,也不是个框架。它多数时候只并发,但审慎的用户或许能够集中地编写代码,而不必在意消息的集散。
下面给出一个例子(stackdemo.c),它是一个粗糙的回显服务。只是简单例子,因此缓冲区是硬编码尺寸的。

#include    <stdio.h>
#include    "./net_common.h"
#include    <sys/stat.h>
#include    "../servcraft/p7/libp7.h"
#include    "../servcraft/p7/p7_root_alloc.h"
#include    "../servcraft/include/model_alloc.h"

void test_echo(void *arg) {
    intptr_t fd_conn_i64crap = (long long) arg;
    int fd_conn = (int) (fd_conn_i64crap & 0xFFFFFFFF);
    char msg;
    memset(msg, 0, sizeof(char) * 32);
    int ret = p7_iowrap_timed(recv, P7_IOMODE_READ, 30000, fd_conn, msg, sizeof(msg), 0);
    switch (ret) {
      case -1:
            printf("internal error\n");
            break;
      case -2:
            printf("p7 timed out\n");
            break;
      default:
            p7_iowrap(send, P7_IOMODE_WRITE, fd_conn, msg, strlen(msg), 0);
    }
    close(fd_conn);
}

void test_timeout(void *arg) {
    long long fd_conn_i64crap = (long long) arg;
    int fd_conn = (int) (fd_conn_i64crap & 0xFFFFFFFF);
    printf("Timed out: %d\n", fd_conn);
}

void test_echo_wrapper(void *arg) {
    p7_coro_concat(test_echo, arg, 2048);
}

void test_startup(void *unused) {
    printf("startup\n");
}

int main(int argc, char *argv[]) {
    __auto_type allocator = p7_root_alloc_get_allocator();
    allocator->allocator_.closure_ = malloc;
    allocator->deallocator_.closure_ = free;
    allocator->reallocator_.closure_ = realloc;
    p7_init(atoi(argv), test_startup, NULL);

    int fd_listen;

    if ((fd_listen = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
      perror("socket");
      exit(1);
    }

    struct sockaddr_in lserv;
    lserv.sin_family = AF_INET;
    lserv.sin_port = htons(8080);
    lserv.sin_addr.s_addr = inet_addr(argv);
    bzero(&(lserv.sin_zero), 8);

    if (bind(fd_listen, (struct sockaddr *) &lserv, sizeof(lserv)) == -1) {
      perror("bind");
      exit(1);
    }
   
    if (listen(fd_listen, 5) == -1) {
      perror("listen");
      exit(1);
    }

    while (1) {
      struct sockaddr_in rcli;
      socklen_t addrlen = sizeof(rcli);
      int fd_conn = p7_iowrap(accept, P7_IOMODE_READ, fd_listen, (struct sockaddr *) &rcli, &addrlen);
      intptr_t fd_conn_i64crap = fd_conn;
      p7_coro_create(test_echo_wrapper, (void *) fd_conn_i64crap, 512);
    }

    return 0;
}

头文件net_common.h是个胡乱拼凑的东西:

/* common header for net app */

#ifndef                NET_COMMON_H_
#define                NET_COMMON_H_

#include      <stdio.h>
#include      <stdlib.h>
#include      <string.h>

#include      <unistd.h>
#include      <sys/types.h>
#include      <sys/wait.h>
#include      <errno.h>
#include      <sys/stat.h>
#include      <sys/socket.h>
#include    <arpa/inet.h>
#include      <netinet/in.h>
#include      <netdb.h>

#include      <sys/mman.h>
#include      <sys/epoll.h>

#include      <signal.h>
#include      <fcntl.h>

#endif                // NET_COMMON_H_
(之前小耗指出这里有reserved name, 确实如此。去掉前缀的下划线。)

然后我试着编译它。

gcc -o stackdemo stackdemo.c -g -O2 -lp7
运行的时候给出线程数和IP地址两个参数。端口也是胡乱编码的8080。
它应当能并发地执行,连接建立之后对一次回显请求的响应要求在30秒内到达,否则视作超时而放弃连接。

这个回显服务做了如下的事情:

[*]启动p7并发,执行监听;
[*]accept一个连接,以这个连接上的fd为参数启动一个协程A; 协程A具有512B尺寸的栈,它以2KB栈尺寸启动另一个协程B, 并等待它结束;
[*]协程B试图按30kms超时recv一条消息,并把它发送回去;无论如何,适当地善后。

{:5_163:}至少它看上去还算简单……

帅气可爱魔理沙 发表于 2016-1-2 03:12:48

{:6_197:}
不明觉厉

iyzsong 发表于 2016-1-2 16:36:05

瞻仰留念{:6_194:}

岩川黑鬼 发表于 2016-1-2 17:37:28


_NET_COMMON_H_ 这 reserved name 是怎么回事?

是我傻逼了。那头文件我大二的时候写的,后来胡乱拷着本地用,几年都忘改header guard...

nadesico19 发表于 2016-1-6 08:51:48

终于有官方文档了,先马后读{:6_188:}

moecmks 发表于 2016-1-6 12:55:31

斯国一

岩川黑鬼 发表于 2016-1-9 14:36:00

nadesico19 发表于 2016-1-6 08:51
终于有官方文档了,先马后读

我懒成狗,文档一直没写……
哪天写个man吧_(:з」∠)_
页: [1]
查看完整版本: [C氵] 非抢占式并发(1):p7简介