第三十七章 POSIX线程(一)
POSIX线程库相关介绍
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都有“pthread_”开头
要使用这些函数库,都需要加入头文件“<pthread.h>”, 链接的时候需要链接“-lpthread”
pthread_create
功能:
创建一个线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
thread : 返回线程ID
attr : 设置线程属性,为NULL时表示使用默认属性
start_routine : 是个函数地址,线程启动后要执行的函数
arg : 传给线程启动函数的参数
返回值:
成功 :0
失败 : 返回错误码
pthread_exit
功能:
线程终止
原型:
void pthread_exit(void *retval);
参数:
retval :retval不要指向一个局部变量
返回值:
无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
pthread_join
功能:
等待线程结束
原型:
int pthread_join(pthread_t thread, void **retval);
参数:
thread :线程ID
retval : 它指向一个指针,指向线程的返回值
返回值:
成功 : 0
失败 : 返回错误码
pthread_detach
功能:
分离线程
原型:
int pthread_detach(pthread_t thread);
参数:
thread :线程ID
返回值:
成功 : 0
失败 : 返回错误码
pthread_self
功能:
返回线程的ID
原型:
int pthread_self(void);
返回值:
总是成功,返回调用此函数的线程ID
pthread_cancle
功能:
取消一个执行中的线程
原型:
int pthread_cancel(pthread_t thread);
参数:
thread : 线程ID
返回值:
成功 : 0
失败 : 返回错误码
错误检查
- 传统的一些函数是,成功返回0, 失败返回-1,并且对全局变量errno赋值以指示错误
- pthread函数出错时不会设置全局变量errno(而大部分其它POSIX函数会这样做)。而是将错误代码通过返回值返回
- pthread同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthread函数的错误设置,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小
进程线程对比
属性 | 进程 | 线程 |
---|---|---|
ID | pid_t | pthread_t |
创建 | fork | pthread_create |
等待 | waitpid | pthread_join |
僵尸 | waitpid | pthread_join、pthread_detach |
退出(自杀) | exit,return | pthread_exit,return |
他杀 | kill | pthread_cancel |
用线程实现回射客户端、服务器端
pserver.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void do_service(int conn)
{
char recvbuf[1024] = {0};
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());
// 使用取地址的方式获取
//int conn = *((int*)arg);
// 使用强制转换的方式获取
// int conn = (int)arg;
// 使用取地址的方式获取,释放空间
int conn = *((int*)arg);
free(arg);
do_service(conn);
printf("exit thread %lu ...\n",pthread_self());
return NULL;
}
int main(void)
{
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(5188);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1",&serv_addr.sin_addr);
int on = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if(bind(listenfd, (struct sockaddr*)(&serv_addr), sizeof(serv_addr)) < 0)
ERR_EXIT("bind");
if(listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int conn;
while(1)
{
if((conn = accept(listenfd, (struct sockaddr *)(&cli_addr), &cli_len)) < 0)
ERR_EXIT("accept");
printf("ip : %s port : %d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
pthread_t tid;
int ret;
// 使用这种方式存在一定的问题,因为是&conn,如果accept返回后,thread_routine还没有来得及处理上一个conn,conn将被改变,导致上一次连接无法处理
// 最好不要使用指针传递,应使用值传递;
//if( (ret = pthread_create(&tid, NULL, thread_routine, (void*)&conn)) != 0 )
//使用这种方式,将int类型装换成无类型指针,int是4个字节,指针也是4个字节;但是这种做法是不可移植的,不同的操作系统,指针所占的字节数不一样
// if( (ret = pthread_create(&tid, NULL, thread_routine, (void*)conn)) != 0 )
//申请一块单独的内存放conn,取出后释放掉
int *p = malloc(sizeof(int));
*p = conn;
if( (ret = pthread_create(&tid, NULL, thread_routine, p)) != 0 )
{
fprintf(stderr, "pthread_create : %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
}
return 0;
}
pclient.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void handler(int sig)
{
printf("recv a sig :%d\n",sig);
exit(EXIT_SUCCESS);
}
int main(void)
{
int sock;
// socket(PF_INET,SOCK_STREAM,0);
if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(5188);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0 )
ERR_EXIT("connect");
pid_t pid;
pid = fork();
if(pid < 0)
ERR_EXIT("fork");
if(pid == 0)
{
char recvbuf[1024] = {0};
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret = read(sock,recvbuf,sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read");
else if(ret == 0)
{
printf("peer close\n");
break;
}
fputs(recvbuf,stdout);
}
close(sock);
kill(getppid(),SIGUSR1);
}
else
{
signal(SIGUSR1,handler);
char sendbuf[1024] = {0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
{
write(sock,sendbuf,strlen(sendbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
close(sock);
}
return 0;
}