所以我按照无用的建议再次更新代码,以便您可以看到可以编译和运行的内容。我从其他东西清理了一些代码,但问题仍然存在。当我给出SIGINT信号时,用valgrind执行它,程序通过正确打印终止的线程结束,但有时它结束时没有内存泄漏,其他人报告了一些内存泄漏我忘在脑后。目前我正在进行没有连接客户端的测试。
我正在尝试创建一个多线程服务器,它使用select()函数和一个大学项目的线程池。用Valgrind执行我的代码我发现在发送SIGINT信号后,有时它会终止而没有错误,而有时它会报告4个“仍然可达”的错误。
我用valgrind启动程序,指定以下标志:
valgrind --leak-check=full --show-leak-kinds=all
这是新代码
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <sys/select.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#define UNIXPATH "./tmp/socket"
#define THREADSINPOOL 8
#define MAXCONNECTION 32
#define SYSCALL(r,c,msg) \
if((r=c)==-1) {perror(msg); exit(errno); }
typedef struct _elem{
long connfd;
struct _elem *next;
}elem;
static volatile sig_atomic_t terminate=0;
int try=0;
int nelem=0;
elem *head=NULL;
elem *corr=NULL;
elem *tmp=NULL;
pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t empty=PTHREAD_COND_INITIALIZER;
pthread_cond_t full=PTHREAD_COND_INITIALIZER;
int update(fd_set set, int fdmax){
for(int i=(fdmax-1);i>=0; i--)
if(FD_ISSET(i,&set)) return i;
return -1;
}
void *threadF(void *arg){
pthread_t self;
long connfd=0;
while(1){
pthread_mutex_lock(&mtx);
if(try==1){
pthread_mutex_unlock(&mtx);
break;
}
while(nelem==0 && try==0)
pthread_cond_wait(&empty,&mtx);
if(try==1){
pthread_mutex_unlock(&mtx);
break;
}
nelem--;
tmp=head;
connfd=tmp->connfd;
tmp=tmp->next;
free(head);
head=tmp;
pthread_cond_signal(&full);
pthread_mutex_unlock(&mtx);
//read & write on tmp->connfd
self = pthread_self();
printf("tmp->connfd: %lu on thread: %lu\n", connfd,self);
close(connfd);
}
pthread_exit(0);
}
void insert(long connfd){
pthread_mutex_lock(&mtx);
while(nelem==MAXCONNECTION && try==0)
pthread_cond_wait(&full,&mtx);
if(try==1){
pthread_mutex_unlock(&mtx);
}else{
nelem++;
elem *new=malloc(sizeof(elem));
new->connfd=connfd;
new->next=NULL;
if(head==NULL){
head=new;
corr=head;
}else{
corr->next=new;
corr=corr->next;
}
pthread_cond_signal(&empty);
pthread_mutex_unlock(&mtx);
}
}
pthread_t *createarray(pthread_t *array){
int i,err;
pthread_t id;
for(i=0;i<THREADSINPOOL;i++){
if((err=pthread_create(&id,NULL,&threadF,NULL))!=0){
fprintf(stderr,"thread\n");
exit(errno);
}
array[i]=id;
}
return array;
}
void destroyArray(pthread_t *array){
void* value=NULL;
int i,tmp;
for (i = 0; i < THREADSINPOOL; i++){
if ((tmp=pthread_join(array[i],&value)) != 0)
printf("error join: %d\n", tmp);
printf("thread: %lu terminated\n",array[i]);
}
free(array);
}
void cleanup(){
unlink(UNIXPATH);
}
void sigint_handler(int signmum){
terminate=1;
}
int main(int argc, char *argv[]) {
cleanup();
atexit(cleanup);
int notused;
sigset_t setmask;
SYSCALL(notused,sigemptyset(&setmask),"SIGEMPTYSET");
SYSCALL(notused,sigaddset(&setmask,SIGINT),"SIGADDSET");
SYSCALL(notused,pthread_sigmask(SIG_SETMASK,&setmask,NULL),"pthread_sigmask");
//create threadspool
pthread_t *array=malloc(THREADSINPOOL*sizeof(pthread_t));
array=createarray(array);
struct sigaction s;
memset(&s,0,sizeof(s));
s.sa_handler=sigint_handler;
s.sa_flags=SA_RESTART;
SYSCALL(notused,sigaction(SIGINT,&s,NULL),"SIGINT");
SYSCALL(notused,sigemptyset(&setmask),"SIGEMPTYSET");
SYSCALL(notused,pthread_sigmask(SIG_SETMASK,&setmask,NULL),"sigmask");
int listenfd;
SYSCALL(listenfd, socket(AF_UNIX, SOCK_STREAM, 0), "socket");
struct sockaddr_un serv_addr;
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strncpy(serv_addr.sun_path, UNIXPATH, strlen(UNIXPATH)+1);
SYSCALL(notused, bind(listenfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr)), "bind");
SYSCALL(notused, listen(listenfd, MAXCONNECTION), "listen");
long fd_c;
int i=0;
fd_set set, rdset;
FD_ZERO(&rdset);
FD_ZERO (&set);
FD_SET(listenfd,&set);
int fd_num=listenfd;
struct timeval tv;
tv.tv_sec=3;
tv.tv_usec=0;
while(1){
if(terminate==1){
break;
}
rdset=set;
if((notused=select(fd_num+1, &rdset, NULL, NULL, &tv))==-1){
if(errno==EINTR){
if(terminate==1){
break;
}else{
perror("select");
exit(EXIT_FAILURE);
}
}
}
for(i=0; i<=fd_num;i++){
if(FD_ISSET(i,&rdset)){
if(i==listenfd){
SYSCALL(fd_c, accept(listenfd, (struct sockaddr*)NULL ,NULL), "accept");
printf("connection accepted\n");
FD_SET(fd_c, &set);
if(fd_c>fd_num) fd_num=fd_c;
}else{
fd_c=i;
}
insert(fd_c);
FD_CLR(fd_c,&set);
if(fd_c==fd_num) fd_num=update(set,fd_num);
}
}
}
pthread_mutex_lock(&mtx);
try=1;
pthread_mutex_unlock(&mtx);
close(listenfd);
pthread_cond_broadcast(&empty);
pthread_cond_signal(&full);
// join on the thread and free(array)
destroyArray(array);
if(tmp)
free(tmp);
return 0;
}
最后,这是我执行代码时有时得到的Valgrind输出:
==7578== Memcheck, a memory error detector
==7578== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==7578== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==7578== Command: ./server
==7578==
^Cthread: 100800256 terminated
thread: 109192960 terminated
thread: 117585664 terminated
thread: 125978368 terminated
thread: 134371072 terminated
thread: 142763776 terminated
thread: 151156480 terminated
thread: 159549184 terminated
==7578==
==7578== HEAP SUMMARY:
==7578== in use at exit: 1,638 bytes in 4 blocks
==7578== total heap usage: 15 allocs, 11 frees, 4,958 bytes allocated
==7578==
==7578== 36 bytes in 1 blocks are still reachable in loss record 1 of 4
==7578== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7578== by 0x401CF99: strdup (strdup.c:42)
==7578== by 0x40187DE: _dl_load_cache_lookup (dl-cache.c:311)
==7578== by 0x4009168: _dl_map_object (dl-load.c:2364)
==7578== by 0x4015576: dl_open_worker (dl-open.c:237)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x4014DA8: _dl_open (dl-open.c:660)
==7578== by 0x519A5AC: do_dlopen (dl-libc.c:87)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x519A663: dlerror_run (dl-libc.c:46)
==7578== by 0x519A663: __libc_dlopen_mode (dl-libc.c:163)
==7578== by 0x4E4B91A: pthread_cancel_init (unwind-forcedunwind.c:52)
==7578== by 0x4E4BB03: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)
==7578==
==7578== 36 bytes in 1 blocks are still reachable in loss record 2 of 4
==7578== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7578== by 0x400BEF3: _dl_new_object (dl-object.c:165)
==7578== by 0x400650C: _dl_map_object_from_fd (dl-load.c:1028)
==7578== by 0x4008C26: _dl_map_object (dl-load.c:2498)
==7578== by 0x4015576: dl_open_worker (dl-open.c:237)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x4014DA8: _dl_open (dl-open.c:660)
==7578== by 0x519A5AC: do_dlopen (dl-libc.c:87)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x519A663: dlerror_run (dl-libc.c:46)
==7578== by 0x519A663: __libc_dlopen_mode (dl-libc.c:163)
==7578== by 0x4E4B91A: pthread_cancel_init (unwind-forcedunwind.c:52)
==7578== by 0x4E4BB03: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)
==7578==
==7578== 384 bytes in 1 blocks are still reachable in loss record 3 of 4
==7578== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7578== by 0x40120BD: _dl_check_map_versions (dl-version.c:293)
==7578== by 0x4015B18: dl_open_worker (dl-open.c:286)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x4014DA8: _dl_open (dl-open.c:660)
==7578== by 0x519A5AC: do_dlopen (dl-libc.c:87)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x519A663: dlerror_run (dl-libc.c:46)
==7578== by 0x519A663: __libc_dlopen_mode (dl-libc.c:163)
==7578== by 0x4E4B91A: pthread_cancel_init (unwind-forcedunwind.c:52)
==7578== by 0x4E4BB03: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)
==7578== by 0x4E4A06F: __pthread_unwind (unwind.c:121)
==7578== by 0x4E42844: __do_cancel (pthreadP.h:283)
==7578== by 0x4E42844: pthread_exit (pthread_exit.c:28)
==7578==
==7578== 1,182 bytes in 1 blocks are still reachable in loss record 4 of 4
==7578== at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7578== by 0x400BBF5: _dl_new_object (dl-object.c:75)
==7578== by 0x400650C: _dl_map_object_from_fd (dl-load.c:1028)
==7578== by 0x4008C26: _dl_map_object (dl-load.c:2498)
==7578== by 0x4015576: dl_open_worker (dl-open.c:237)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x4014DA8: _dl_open (dl-open.c:660)
==7578== by 0x519A5AC: do_dlopen (dl-libc.c:87)
==7578== by 0x4010563: _dl_catch_error (dl-error.c:187)
==7578== by 0x519A663: dlerror_run (dl-libc.c:46)
==7578== by 0x519A663: __libc_dlopen_mode (dl-libc.c:163)
==7578== by 0x4E4B91A: pthread_cancel_init (unwind-forcedunwind.c:52)
==7578== by 0x4E4BB03: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126)
==7578==
==7578== LEAK SUMMARY:
==7578== definitely lost: 0 bytes in 0 blocks
==7578== indirectly lost: 0 bytes in 0 blocks
==7578== possibly lost: 0 bytes in 0 blocks
==7578== still reachable: 1,638 bytes in 4 blocks
==7578== suppressed: 0 bytes in 0 blocks
==7578==
==7578== For counts of detected and suppressed errors, rerun with: -v
==7578== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
新的Valgrind输出
==3920== Memcheck, a memory error detector
==3920== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3920== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==3920== Command: ./server
==3920==
^C
==3920==
==3920== Process terminating with default action of signal 2 (SIGINT)
==3920== at 0x4E3FE6D: __pthread_initialize_minimal (nptl-init.c:433)
==3920== by 0x4E3F588: ??? (in /lib/x86_64-linux-gnu/libpthread-2.23.so)
==3920== by 0x400044F: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
==3920== by 0x4010679: call_init.part.0 (dl-init.c:58)
==3920== by 0x4010834: call_init (dl-init.c:104)
==3920== by 0x4010834: _dl_init (dl-init.c:87)
==3920== by 0x4000C69: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
==3920==
==3920== HEAP SUMMARY:
==3920== in use at exit: 0 bytes in 0 blocks
==3920== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==3920==
==3920== All heap blocks were freed -- no leaks are possible
==3920==
==3920== For counts of detected and suppressed errors, rerun with: -v
==3920== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
但有时它会在没有内存泄漏的情况下结束,其他人会报告我留下的一些内存泄漏。
这可能是因为程序在终止之前没有清理其已接受连接的链表。
但是您的代码中还有其他一些令人不安的问题。
terminate
变量不是线程安全的。不要被sig_atomic_t
类型的名称混淆:it is not an atomic datatype。信号处理程序使用它可以安全地与接收信号的线程进行通信,但是在不使用适当的同步对象(例如互斥锁)的情况下在线程之间进行通信是不安全的。此外,尽管需要与信号处理程序一起使用,making it volatile
also fails to confer thread safety。这是您当前代码的问题,因为当您发送SIGINT
时,它可以由任何线程处理。
我建议通过阻止SIGINT
除了主线程之外的所有解决方案。当进程收到一个进程指示的信号时,它会排队等待当时没有被阻塞的线程,所以你不必担心成功接收信号。每个线程都有自己的信号掩码,其初始值是从其父线程继承的。因此,我建议在启动所有工作程序之前在主线程中阻塞SIGINT
,然后在工作程序全部运行后仅在主线程中解除阻塞。try
,nelem
以及由head
领导的链表的所有内容。
threadF()
在不持有互斥锁的情况下检查try
main()
没有持有互斥锁就设置了try
因此,程序的行为是不确定的。在实践中,这似乎不太可能与“泄漏”有关,但它可能会导致您的程序偶尔无法完全关闭。原则上,任何事情都可能发生。在实践中,最可能的影响是您的程序偶尔无法干净地关闭。