线程的意外行为
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, ¶m) != 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(¶m);
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++;
很可能,它们中的每一个都被翻译成不止一条汇编指令(mov
、inc/add
、mov
)。在我看来,您在竞争条件方面存在问题。
编辑:
让我们假设增量操作被翻译成以下机器指令:
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
事件。
我正在用 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, ¶m) != 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(¶m);
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++;
很可能,它们中的每一个都被翻译成不止一条汇编指令(mov
、inc/add
、mov
)。在我看来,您在竞争条件方面存在问题。
编辑:
让我们假设增量操作被翻译成以下机器指令:
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
事件。