文章目录

  • ​​1.POSIX线程库相关函数​​
  • ​​2.进程和线程的对比​​
  • ​​3.用线程实现回射客户/服务器​​

1.POSIX线程库相关函数

  • POSIX线程库
    (1)与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以pthread_开头的
    (2)要使用这些函数库,要通过引入头文件<pthread.h>
    (3)链接这些线程函数库时,要使用编译器命令的 -lpthread
  • pthread_creat函数
功能:创建一个新的线程(线程是单独的控制序列)

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

参数:
thread:返回线程ID
attr:设置线程的属性,attr为NULL,表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数

返回值:
成功返回0;
失败返回错误码;
  • 错误检查:每个线程都有自己的errno变量
    (1)传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
    (2)pthreads函数出错时,不会设置全局变量errno(而大部分其它POSIX函数会这样做)。而是将错误代码通过返回值返回。
    (3)pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。
    对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销要小。
  • pthread_exit函数
功能:线程终止

原型:
void pthread_exit(void *value_ptr);

参数:
value_ptr:value_ptr不要指向一个局部变量

返回值:
无返回值,与进程一样,线程结束的时候,无法返回到他的调用者(自身)
  • pthread_join函数
功能:等待线程结束

原型:
int pthread_join(pthread_t thread, void **retval);

参数:
thread:线程ID
value_ptr:他指向一个指针,后者指向线程的返回值

返回值:
成功返回0;
失败返回错误码;
  • pthread_self函数
功能:返回线程ID

原型:
pthread_t pthread_self(void);

返回值:
成功返回0
  • pthread_cancel函数
功能:取消一个执行中的线程

原型:
int pthread_cancel(pthread_t thread);

参数:
thread:线程ID

返回值:成功返回0;
失败返回错误码;
  • pthread_detach函数:避免僵尸线程
功能:将一个线程分离

原型:
int pthread_detach(pthread_t thread);

参数:
thread:线程ID

返回值:
成功返回0;
失败返回错误码;
  • eg:NetworkProgramming-master (1)\NetworkProgramming-master\P37thread_create.c
//
// Created by wangji on 19-8-14.
//

// p37 poxis 线程(一)

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

using namespace std;


#define ERR_EXIT(m) \
do \
{ \
perror(m); \ //perror检查的是全局的error变量
exit(EXIT_FAILURE); \
} while(0);

void * start_routine (void *arg)
{
pthread_detach(pthread_self());
for (int i = 0; i < 20; ++i)
{
printf( "B");
fflush(stdout);
usleep(20);
if(i == 3)
pthread_exit("ABC");//pthread_exit退出
}
sleep(3);
//return (char *)"hello";//整个代码执行完毕退出
}

int main(int argc, char** argv) {
int ret;
pthread_t tid;

ret = pthread_create(&tid, NULL, start_routine, NULL);//线程id由tid返回
if (ret != 0)
{
fprintf(stderr,"pthread_creat:%s\n",strerror(ret));//错误码通过函数返回
ERR_EXIT("pthread_create");
}

//主线程结束,整个进程就会结束
for (int i = 0; i < 20; ++i)
{
printf( "A");
fflush(stdout);
usleep(20);
}

void *retval;
// if (pthread_join(tid, &retval) != 0)
// {
// ERR_EXIT("pthread_join");
// }

//return (char *)"hello"中的hello也会传递到retval
if ((ret = pthread_join(tid, &retval)) != 0 )//等待新创建的线程结束,将pthread_exit中的ABC传递到retval,类似waitpid
{
fprintf(stderr,"pthread_join:%s\n",strerror(ret));//错误码通过函数返回
ERR_EXIT("pthread_create");
}
printf("return msg=%s\n", (char *)retval);

return 0;
}

=================================================Makefile文件=========================================
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=01thread
%.c:%.o
$(CC) $(CFLAGS) -c $< -o $@
01thread:01thread.o
$(CC) $(CFLAGS) -c $^ -o $@ -lpthread
clean:
rm -f *.o $(BIN)
  • 测试:
    2个线程是交替运行的,先运行谁,这取决于系统如何调度线程的

2.进程和线程的对比

进程

线程

pid_t

thread_t

fork

pthread_creat

等待子进程waipid

pthread_join

退出进程,可以在任何位置调用exit

退出线程,在线程控制序列的任何位置调用pthread_exit

退出线程,在线程控制序列的任何位置调用pthread_exit

在线程入口函数中调用return(表示线程执行完毕,则线程消亡)

  • 僵尸进程与僵尸线程
    (1)僵尸进程(产生:子进程结束,父进程还没结束,那么子进程会保留一个状态直到父进程调用wait或者waitpid,此时僵尸进程的状态才消失)
    (2)僵尸线程(产生:子线程结束了,而主线程没有调用join,那么子线程将处于僵尸线程的状态,因为有些主线程不会使用join,所以可以调用pthread_detach脱离一个线程,该方法不会产生僵尸线程)
  • 线程的结束方式
    (1)自杀
    退出线程,在线程控制序列的任何位置调用pthread_exit;
    在线程入口函数中调用return(表示线程执行完毕,则线程消亡);
    (2)他杀
    pthread_cancel(一个线程杀死另外一个线程)

3.用线程实现回射客户/服务器

  • eg:NetworkProgramming-master (1)\NetworkProgramming-master\P37threadechosrv.c
//
// Created by wangji on 19-8-6.
//

// p37 poxis 线程(一)

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


using namespace std;

struct packet
{
int len;
char buf[1024];
};

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

ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; // 剩余字节数
ssize_t nread;
char *bufp = (char*) buf;

while (nleft > 0)
{
nread = read(fd, bufp, nleft);
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
} else if (nread == 0)
{
return count - nleft;
}

bufp += nread;
nleft -= nread;
}
return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;

while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
}
else if (nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}

ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看传入消息
if (ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf; // 当前指针位置
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, buf, nleft);
if (ret < 0)
{
return ret;
}
else if (ret == 0)
{
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
{
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft)
{
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
{
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}

void echo_srv(int connfd)
{
char recvbuf[1024];
// struct packet recvbuf;
int n;
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
break;
}

fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
}

}


void * start_routine(void *arg)
{
pthread_detach(pthread_self());

/*
(1)方法1:
int conn = *((int*)arg);//已连接套接字传递过来了
(2)方法2:
int conn = arg;//已连接套接字传递过来了
(3)方法3:
int conn = *((int*)arg);//已连接套接字传递过来了
free(arg);
*/
int conn = arg;//已连接套接字传递过来了
echo_srv(conn);//调用回射服务器
printf("exiting thread...\n");
// return (char *)"hello";
return NULL;
}

int main(int argc, char** argv) {
// 1. 创建套接字
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}

// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);

int on = 1;
// 确保time_wait状态下同一端口仍可使用
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
ERR_EXIT("setsockopt");
}

// 3. 绑定套接字地址
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
ERR_EXIT("bind");
}
// 4. 等待连接请求状态
if (listen(listenfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
// 5. 允许连接
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);


// 6. 数据交换
pid_t pid;
while (1) {
int connfd;
if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {
ERR_EXIT("accept");
}

printf("ip = %s, ", inet_ntoa(peeraddr.sin_addr));
printf("port = %d\n", ntohs(peeraddr.sin_port));

pthread_t thread;

/*
(1)方式1,如果以前的start_routine没有将connfd取走的话,新的connfd又来了,他可能指向新的connfd,而原来的connfd就取不到了
这称之为:race condition静态问题,所以不要用这样的指针传递,可以用下面的(2)值传递
int ret = pthread_create(&thread, NULL, start_routine, (void *)&connfd);//start_routine线程入口函数

(2)方式2:但是(void *)在32bit机器上是4个字节,在64bit机器上8字节,所以是不可移植的
int ret = pthread_create(&thread, NULL, start_routine, (void *)connfd);//start_routine线程入口函数

(3)方式3:避免了(1)的静态问题,还解决了(2)的可移植问题
int *p = malloc(sizeof(int));
*p = connfd;
int ret = pthread_create(&thread, NULL, start_routine, p);//start_routine线程入口函数
*/
//每个线程处理一个客户端连接
int ret = pthread_create(&thread, NULL, start_routine, (void *)connfd);//start_routine线程入口函数
if (ret != 0)
{
fprintf(stderr,"pthread_create:%s\n",strerror(ret));
ERR_EXIT("pthread_create");//可直接写成exit(EXIT_FAILURE);
}
/*
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)//子进程处理回射服务
{
close(listenfd);
echo_srv(connfd);//调用回射服务器
exit(EXIT_SUCCESS);
}
else//父进程关闭连接套接字
close(connfd);
*/

}
// 7. 断开连接
close(listenfd);


return 0;
}

================================Makeflie==================================
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=01thread echosrv echocli
all:$(BIN)
%.c:%.o
$(CC) $(CFLAGS) -c $< -o $@
01thread:01thread.o
$(CC) $(CFLAGS) -c $^ -o $@ -lpthread
echosrv:echoerv.o
$(CC) $(CFLAGS) -c $^ -o $@ -lpthread
clean:
rm -f *.o $(BIN)
  • 测试结果
    一个客户端过来,就创建一个新的线程来处理它

    客户端退出,线程也就结束了