POSIX线程,线程的客户/服务通信(pthread_join,pthread_exit,pthread_detach,pthread_self)

我们所熟知的线程函数:

1,pthread_create,pthread_join,pthread_exit,pthread_detach,pthread_self

2,如何避免产生僵尸线程(进程)

3,多线程引发的客户/服务通信


POSIX线程库函数介绍:

1,与线程有关的函数构成了一个完整的系列,绝大多数的函数的开头都是以:“pthread_t”打头的

2,要使用这些库函数,我们必须引入头文件<pthread.h>

3,链接这些线程函数库时,要使用编译器命令的“- lpthread”选项





函数:

1,线程创建函数pthread_create

  

       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

  thread:该参数是一个指针,当线程创建成功时,返回创建线程的ID

  attr:用于指定线程的属性,NULL表示默认的属性

  start_routine:该参数为一个函数指针,指向线程创建后要调用的函数。这个被线程调用的函数也

       称为线程函数

  arg:参数指向传递给线程函数的参数

  注意的是:线程创建成功后,pthread_create函数返回0。若不为0则说明创建线程失败。常见的错误码为:

       EAGAIN(表示系统限制创建新的线程,如:线程数目过多),EINVAL(表示第二个参数代表的

       线程属性值非法。)

  线程创建成功后,新创建的线程开始运行第3个参数所指向的函数,原来的线程继续运行


  传统的一些函数是成功返回0,失败返回-1,并且对全局变量errno赋值以提示错误,而pthread

  出错的时候不会设置全局变量errno(但是其它的POSIX函数会这样处理),而是将错误代码通过返回

  值返回


  而我们之前经常用的那个perror就是用来检测全局变量的,所以下面这个函数用不了了:

  #define ERR_EXIT(m)\
        do\
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

  上面我们知道pthread不对errno这个全局变量进行处理,但是,别忘了pthread_create中的第三个

  参数,它指向的函数有可能会调用其它的一些函数,可能会产生错误,将其结构保存到errno中(这些东

  西并不矛盾,但是别忘了,在多线程并发的时候,不可能让某一个变量改变总的全局变量,所以线程库就对

  每一个新创建的线程都提供一个errno变量

  但是,对于pthread函数的错误,通过读取返回值的判定要比读取全局变量的开销要小的多

  也就是说:当我们创建一个线程,第三个参数所指向的线程函数如果还调用了其它线程库的函数,我们是可以通

  过返回值来判定的。而对于其它的函数,仍然是返回失败是-1


  这样我们来实现一个这样的功能,主线程这边打印A,新创建的线程打印B(通常不称之为:子线程,因为两者没

  有父子之称,但是可以把第一个初始的线程称为主线程,新创建的一定不能是子线程)


#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

#define ERR_EXIT(m)\
        do\
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

void * thread_routine(void *arg)
{
        int i;
        for(i = 0; i<20; i++)
        {
                printf("B");
                fflush(stdout);
        }
        return 0;
}

int main(void)
{
        pthread_t tid;
        int ret, i;
        if((ret = pthread_create(&tid, NULL, thread_routine, NULL)!= 0))
        {
                fprintf(stderr, "pthread_create:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }

        for(i = 0; i<20; i++)
        {
                printf("A");
                fflush(stdout);
        }
        return 0;
}
  运行结果:

  

  可以看到,我们的编译过程加入了(-lpthread),程序中出现了警告(这并不影响我们的程序)

  程序的结果如此的合理,

  但是,有的时候我们的出现全是A的情况,B没有打印输出的情况,这可能是由于:主线程执行的太快了,已经

  结束了,但是新创建的线程还没有被调度到

  从上面的结果也可以看到,两者是交替运行的,是在抢占时间片

  为了更清楚的看到交替,我们在线程中都假如usleep(20).我们再来看看结果:

  


  可以看到,多次的运行结果不一样,这取决于系统是如何调度线程的


  还有一个问题就是,主线程这边需要睡眠,等待新创建的线程运行结束,如果没有等待,就有可能主线程这边

  还已经结束,新创建的线程还没执行完毕,所以一般情况下,要让主线程等待一段时间

  如果不等待的话:

  

  可以看到上面两者不齐,也就算新的线程还没有执行完成。。。。。。

  

int main(void)
{
        pthread_t tid;
        int ret, i;
        if((ret = pthread_create(&tid, NULL, thread_routine, NULL)!= 0))
        {
                fprintf(stderr, "pthread_create:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }

        for(i = 0; i<20; i++)
        {
                printf("A");
                fflush(stdout);
                usleep(20);
        }
        printf("\n");
        return 0;
}
  

  程序如上

  为了解决上面的问题,我们选择了在主线程之后加入sleep(1).

  那么我们能不能有一个新创建的函数等待新创建的函数结束呢????

  

  所以这里我们可以用:pthread_join来等待新创建的线程的结束

  

  #include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);
  接收两个参数:

  thread:线程ID

  retval:是一个无类型指针的指针

  同时,这个函数也是成功返回0,错误返回错误代码

int main(void)
{
        pthread_t tid;
        int ret, i;
        if((ret = pthread_create(&tid, NULL, thread_routine, NULL)!= 0))
        {
                fprintf(stderr, "pthread_create:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }

        for(i = 0; i<20; i++)
        {
                printf("A");
                fflush(stdout);
                usleep(20);
        }

        if((ret = pthread_join(tid, NULL) != 0))
        {
                fprintf(stderr, "pthread_join:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }
        printf("\n");
        return 0;
}
  pthread_join(等待新创建的线程结束),如果没有这个函数的话,那么是不会进行等待的

  类似与进程的waitpid等待其结束。。。。



  线程的终止函数pthread_exit(同样的,类似与进程的exit函数)

       #include <pthread.h>

       void pthread_exit(void *retval);
  参数:retval传的一个参数,如果是空的话,那么pthread_join的第二个参数就算NULL

     如果非空的话,那么就将该字符传给pthread_join函数的第二个参数。

     

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

#define ERR_EXIT(m)\
        do\
        {\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)

void * thread_routine(void *arg)
{
        int i;
        for(i = 0; i<20; i++)
        {
                printf("B");
                fflush(stdout);
                usleep(20);
                if(i == 3)
                        pthread_exit("ABC");
        }
        return 0;
}




int main(void)
{
        pthread_t tid;
        int ret, i;
        if((ret = pthread_create(&tid, NULL, thread_routine, NULL)!= 0))
        {
                fprintf(stderr, "pthread_create:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }

        for(i = 0; i<20; i++)
        {
                printf("A");
                fflush(stdout);
                usleep(20);
        }

        void *value;
        if((ret = pthread_join(tid, &value) != 0))
        {
                fprintf(stderr, "pthread_join:%s\n", strerror(ret));
                exit(EXIT_FAILURE);
        }
        printf("\n");
        printf("return msg = %s\n", (char*)value);
        return 0;
}
  运行结果:

  


  更为准确的说法:应该是新创建的线程的返回值应该为作为pthread_join的第二个参数

  如下:当我们把新创建的线程写成这样的时候:

void * thread_routine(void *arg)
{
        int i;
        for(i = 0; i<20; i++)
        {
                printf("B");
                fflush(stdout);
                usleep(20);
//              if(i == 3)
//                      pthread_exit("ABC");
        }
        sleep(3);
        return "def";
}
  运行结果:

  

  也就是说:线程的调用可以在任何地方调用pthread_exit,进程的调用页可以在任何地方调用exit

  当然,我们还可以在main中调用return表示进程的退出

  在调用线程的函数中调用return也可以退出线程



当然:这里还涉及到僵进程,这是什嘛情况吗???

子进程结束了,父进程还没有结束,那么这个时候,子进程会保留一个状态,直到父进程调用wait或者waitpid,那么才不会出现僵进程,这个僵进程的状态才会结束(从子进程结束到父进程调用wait函数期

间)

同样的,也有僵线程的概念:

新创建的线程结束了,但是主线程这边并没有调用pthread_join,那么此刻也就仍然处于僵线程的状态中了。

实际上,我们可以设置线程的属性,来避免僵线程,因为有一些程序,我们不会去调用pthread_join来

避免僵线程的,那么这个时候我们会将线程的属性设置为脱离的(如果:我们一开始没有设置线程的属性为脱离的

,那么我们在过程中可以调用pthread_detach方法,来进行脱离),那么对于脱离的线程就不会产生僵线程




返回当前线程的ID(pthread_self)

       #include <pthread.h>

       pthread_t pthread_self(void);
同上,还是成功的时候返回0,错误的时候返回错误码



取消一个执行中的线程(或者杀死一个线程):pthread_cancel

   #include <pthread.h>

  int pthread_cancel(pthread_t thread);
同样类比进程中的杀死,kill


所以说:线程的结束有两种可能:

1,自杀(return,pthread_exit)

2,他杀(pthread_cancel)


下面我们继续编写一个线程下的回射通信:

服务端:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)

void echo_srv(int conn)
{
	char recvbuf[1024];
	while(1)
	{
		memset(recvbuf, 0, sizeof(recvbuf));
		int ret = read(conn, recvbuf, sizeof(recvbuf));
		if(ret == 0)
		{
			printf("client close\n");
			break;
		}
		else if(ret == -1)
			ERR_EXIT("read");
		fputs(recvbuf, stdout);
		write(conn, recvbuf, ret);
	}
}


void *thread_routine(void *arg)
{
	int conn = (int)arg;
	echo_srv(conn);
	printf("exiting thread.......\n");
	return NULL;
}


int main(void)
{
	int listenfd;
	if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		ERR_EXIT("socket");
	
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	int on = 1;
	if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt");

	if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");

	if(listen(listenfd, SOMAXCONN) < 0)
		ERR_EXIT("listen");

	struct sockaddr_in peeraddr;
	socklen_t peerlen = sizeof(peeraddr);

	int conn;
	pid_t pid;
	while(1)
	{
		if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
			ERR_EXIT("accept");
		printf("ip = %s port = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
/*		pid = fork();
		if(pid == -1)
			ERR_EXIT("fork");
		if(pid == 0)
		{
			close(listenfd);
			echo_srv(conn);
			exit(EXIT_SUCCESS);	
		}
		else
			close(conn);
*/
//上面这段代码是用进程来实现的,我们将其用线程来实现
	
		pthread_t tid;
		int ret;
		if((ret = pthread_create(&tid, NULL, thread_routine, (void*)conn)) != 0)
		{
			fprintf(stderr, "pthread_create:%s\n", strerror(ret));
			exit(EXIT_FAILURE);
		}
	}
	return 0;
}
客户端:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <stdlib.h>  
#include <string.h>  
#include <arpa/inet.h>  
#include <netinet/in.h>  
                                                                                                                                                  
  
#define ERR_EXIT(m)                    \
        do{\
                perror(m);\
                exit(EXIT_FAILURE);\
        }while(0)  
  
  
int main(){  
  
        int sock;      //socket返回值,类似于文件描述符,也成为套接字  
        if((sock = socket(AF_INET,  SOCK_STREAM,  IPPROTO_TCP)) < 0)  
                ERR_EXIT("SOCKET");  
  
  
        struct  sockaddr_in servaddr;                                //inin IPv4  
        memset(&servaddr,  0 , sizeof(servaddr));                 //inint memory  
        servaddr.sin_family = AF_INET;  
        servaddr.sin_port  = htons(5188);  
        servaddr.sin_addr.s_addr  = inet_addr("127.0.0.1");  
  
        if (connect(sock,  (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)  
                ERR_EXIT("CONNECT");  
  
        char sendrec[1024] = {0};  
        char recerec[1024] = {0};  
          
        while( fgets(sendrec  , sizeof(sendrec),  stdin) != NULL){  
                write(sock , sendrec, strlen(sendrec));  
                read(sock,   recerec, sizeof(recerec));  
  
                printf("客户端:  %s\n", recerec);  
  
                memset(sendrec , 0,  sizeof(sendrec));  
                memset(recerec,  0,  sizeof(sendrec));  
  
        }  
        close(sock);  
} 

运行结果:


可以发现,我们的程序是么有问题的!!!

那么我们的线程是如何退出的呢???

客户端结束之后了,使得服务端的read函数返回为0,然后紧接着打印”exiting thread“,然后就退出了。

  另外每个线程执行完毕,实际上是处于一个僵的状态。。。。很简单,只是因为我们这里并没有调用

  pthread_join函数,所以说:我们要避免僵线程的出现。

  我们要在进入pthread_routine这个函数的时候,需要调用一个分离函数(pthread_detach),将

  线程设置为分离的状态

void *thread_routine(void *arg)
{
        pthread_detach(pthread_self);
        //将当前这个线程设置为分离线程,避免了称为僵尸线程
    //通过pthread_self这个函数获取自身的id号,作为参数
        int conn = (int)arg;
        echo_srv(conn);
        printf("exiting thread.......\n");
        return NULL;
}

  当然,运行结果和上面是一样的,只是避免了成为一个僵线程而已。。。。。。。。


  但是,但是,有的时候,我们的处理过程都是这样的!!!

  ret = pthread_create(&tid, NULL, thread_routine, (void*)&conn);

  那么这个写法跟上面我们的处理过程有什么区别吗???

  运行结果也是如此,看不出来有什么不太对的地方啊!!!!

  对于以前的我们的这一种:

  当新的线程还没有执行的时候,主线程accept再次的调用,又再次的返回了一个conn,

  而对于pthread_create的第四个参数,是一个指针,指向了一个已经变换的值,

  所以说:

void *thread_routine(void *arg)
{
	int conn = (int)arg;
	echo_srv(conn);
	printf("exiting thread.......\n");
	return NULL;
}
  这边获得的conn也就是一个已经变换了的结果,所以也就相当于丢弃了第一次的套接字,出现了一个很大

  的问题。。。。。


  所以说:我们最好不要用指针传递。可以用之前我们所说的那种:值传递(int),由于指针刚好也是4个字

  节,因而也能保存这4个字节,但是这种做法是不可移植的,如果是64位的话,那么指针就算8位了,


  那么有没有什嘛更好的方法,更好的方案呢???

  调用之前,重新分配一块内存空间,然后在用完之后,将其释放掉,整个程序如下所说:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)

void echo_srv(int conn)
{
	char recvbuf[1024];
	while(1)
	{
		memset(recvbuf, 0, sizeof(recvbuf));
		int ret = read(conn, recvbuf, sizeof(recvbuf));
		if(ret == 0)
		{
			printf("client close\n");
			break;
		}
		else if(ret == -1)
			ERR_EXIT("read");
		fputs(recvbuf, stdout);
		write(conn, recvbuf, ret);
	}
}


void *thread_routine(void *arg)
{
	pthread_detach(pthread_self);
	//将当前这个线程设置为分离线程,避免了称为僵尸线程
    //通过pthread_self这个函数获取自身的id号,作为参数
	//int conn = (int)arg;
	int conn = *((int *)arg);
	free(arg);
	echo_srv(conn);
	printf("exiting thread.......\n");
	return NULL;
}


int main(void)
{
	int listenfd;
	if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		ERR_EXIT("socket");
	
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(5188);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	int on = 1;
	if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt");

	if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");

	if(listen(listenfd, SOMAXCONN) < 0)
		ERR_EXIT("listen");

	struct sockaddr_in peeraddr;
	socklen_t peerlen = sizeof(peeraddr);

	int conn;
	pid_t pid;
	while(1)
	{
		if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
			ERR_EXIT("accept");
		printf("ip = %s port = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
/*		pid = fork();
		if(pid == -1)
			ERR_EXIT("fork");
		if(pid == 0)
		{
			close(listenfd);
			echo_srv(conn);
			exit(EXIT_SUCCESS);	
		}
		else
			close(conn);
*/
//上面这段代码是用进程来实现的,我们将其用线程来实现
	
		pthread_t tid;
		int ret;
/*
		if((ret = pthread_create(&tid, NULL, thread_routine, (void*)conn)) != 0)
		{
			fprintf(stderr, "pthread_create:%s\n", strerror(ret));
			exit(EXIT_FAILURE);
		}
*/
		//但是,但是,如果我们按这种方式来处理呢???
//		ret = pthread_create(&tid, NULL, thread_routine, (void*)&conn);
 		//为了简单,这里也不对错误进行处理了
		//上面这种方法方式是不可移植的,我们必须得找到一个更好的方法
	
		int *p = malloc(sizeof(int));
		p = conn;   //将套接字保存到当前这个地址上面来
		pthread_create(&tid, NULL, thread_routine, p);
		
		
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值