线程的意外行为

unexpected behavior of threads

我正在用 C 编写一个负载测试 HTTP 客户端,下面给出了部分代码,

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <sched.h>
#include <sys/time.h>
#include <fcntl.h>

typedef struct args_s {
    volatile int t_requested;
    volatile int t_created;
    volatile int c_created;
    volatile int r_created;
    volatile int c_response;
} args_t;

#define HOST "198.168.0.221"

#define CRLF "\r\n"

#define REQUEST "GET / HTTP/1.1" CRLF \
        "Host: 198.168.0.221" CRLF \
        "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0" CRLF \
        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" CRLF \
        "Accept-Language: en-US,en;q=0.5" CRLF \
        "Accept-Encoding: gzip, deflate" CRLF \
        "Connection: keep-alive" CRLF \
        CRLF


static size_t socket_write(int sock_fd, char *buf, size_t size)
{
    size_t n, tot = 0;

  again:
    errno = 0;

    if ((n = write(sock_fd, buf, size)) != -1) {
    tot += n;
    if (tot < size) {
        goto again;
    }
    if (tot == size) {
        return size;
    }
    return -1;
    }
    if (errno == EINTR || errno == EAGAIN) {
    goto again;
    }
    return -1;
}

static size_t socket_read(int sock_fd, char *buf, size_t size)
{
    size_t n;

  again:
    errno = 0;

    if ((n = read(sock_fd, buf, size)) != -1) {
    return n;
    }
    if (errno == EINTR || errno == EAGAIN) {
    goto again;
    }

    return -1;
}

static void *client_thread(args_t * param)
{
    struct sockaddr_in addr = {
    .sin_family = AF_INET,
    .sin_port = htons(80),
    .sin_addr.s_addr = inet_addr(HOST)
    };
    int sock_fd;
    char res[4096];

    unshare(CLONE_FILES);

    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    return "socket() failed";
    }

    if (connect(sock_fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
    close(sock_fd);
    return "connect() failed";
    }

    if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == -1) {
    close(sock_fd);
    return "cant make nonblock";
    }

    param->c_created++;

    if (socket_write(sock_fd, REQUEST, sizeof(REQUEST) - 1) !=
    (sizeof(REQUEST) - 1)) {
    close(sock_fd);
    return "write() failed";
    }

    param->r_created++;

    if (socket_read(sock_fd, res, 4096) == -1) {
    close(sock_fd);
    return "read() failed";
    }

    param->c_response++;
    close(sock_fd);
    return NULL;
}

void report_show(volatile args_t * param)
{
    printf("\n\nREPORTS\n----------------------\n");
    printf("Number of threads requested : %ld \n", param->t_requested);
    printf("Number of threads created : %ld \n", param->t_created);
    printf("Number of connections created : %ld \n", param->c_created);
    printf("Number of requests sent : %ld \n", param->r_created);
    printf("Number of response recieved : %ld \n", param->c_response);
    printf("Number of response failed : %ld \n\n",
       param->r_created - param->c_response);
}


void timer()
{
    int i=0;
    while(1) {
    sleep(1);
        printf("\nTime taken : %d\x1B[A",i++);
    }
}



int main()
{
    int pid,i;
    char c;
    char *ret;
    static volatile args_t param;

    param.t_requested = 30000;
    pthread_t t[param.t_requested];
    if(!(pid = fork())) {
    timer();
    exit(0);
    }

    for (i = 0; i < param.t_requested; i++) {
    if (pthread_create(&t[i], NULL, client_thread, &param) != 0) {
        printf("thread creation failed\n");
        return -1;
    }
    param.t_created++;
    }

    for (i = 0; i < param.t_requested; i++) {
    if(pthread_join(t[i], &ret) != 0) {
        printf("unable to join\n");
        return -1;
    }
    if (ret != NULL) {
        printf("%s\n", ret);
        return -1;
    }
    }

    kill(pid,SIGINT);
    report_show(&param);
    return 0;
}

当我运行这段代码时,我得到如下输出,

[ajith@localhost Pipe_Send_Recv_Capability_Test]$ ./a.out

Time taken : 3

REPORTS
----------------------
Number of threads requested : 30000
Number of threads created : 30000
Number of connections created : 29983
Number of requests sent : 29997
Number of response recieved : 29988
Number of response failed : 9

此处,连接创建值 (29983) 小于请求发送值 (29997)。在代码中没有办法发生这种情况。因为每当 param->r_created 递增时 param->c_created 也会在它之前递增。那为什么会这样呢?

您没有同步增量:

param->c_created++;
...
param->r_created++;
...
param->c_response++;

很可能,它们中的每一个都被翻译成不止一条汇编指令(movinc/addmov)。在我看来,您在竞争条件方面存在问题。

编辑:

让我们假设增量操作被翻译成以下机器指令:

mov eax, c_created 
inc eax
mov c_created, eax

现在想象一下,两个线程正在并行执行这些指令,并且发生了以下执行顺序:

mov eax, c_created <-- THREAD1 eax = <initial>c_created
inc eax
mov c_created, eax

mov eax, c_created <-- THREAD2 eax = <initial>c_created
inc eax            <-- THREAD1 eax = <initial>c_created + 1
mov c_created, eax

mov eax, c_created
inc eax            <-- THREAD2 eax = <initial>c_created + 1
mov c_created, eax <-- THREAD1 <new>c_created = <initial>c_created + 1

mov eax, c_created
inc eax
mov c_created, eax <-- THREAD2 <new>c_created = <initial>c_created + 1

现在你清楚地看到,尽管两个线程都执行了递增操作,但 c_created 的新值有可能会递增 1 而不是 2 事件。