一.线程的基本概念
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位. 所有的线程都是在同一进程空间运行,这也意味着多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。我们看到的进程的切换,切换的也是不同进程的主线程。一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
二,进程与线程的关系和区别
2.1 进程
- 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。
- 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
- 进程是具有独立功能的程序在其数据集合上运行的过程,他是系统调度和资源分配的一个独立单位。
2.2 线程和进程的区别 - 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
- 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小
- 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
- 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
- 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
三.创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create()函数是操作系统创建线程的函数,返回值为0表示创建成功。返回-1表示创建失败。
参数解析:
- *- pthread_t thread:
他用来返回该线程的线程ID。每个线程都能够通过pthread_self()来获取自己的线程ID(pthread_t类型)。 - *const pthread_attr_t attr:
是线程的属性,其类型是pthread_attr_t类型,默认值为NULL。 - ** void *(*start_routine) (void *)**:
start_routine是一个函数指针,它指向的函数原型是 void *func(void *),这是所创建的子线程要执行的任务(函数); - void *arg:
默认值为NULL,若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有
子线程执行都会终止。这时整个进程结束或僵死,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态。线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:
- 在任何一个时间点上,线程都是可分离或者可会合的。一个可会合的线程能够被其它线程收回其资源和杀死。在其他线程回收之前,他的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或者杀死,他的存储器资源在它终止时系统自动释放。
- 在默认的情况下,线程是非分离状态的,这种情况下,原有的线程
等待创建的线程结束,只有当pthread_join函数返回时,创建的线程才算终止,释放自己占用的系统资源,而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
四.多线程服务器流程
五.代码实例
多线程服务器代码:
/*********************************************************************************
* Copyright: (C) 2021 jiaoer237
* All rights reserved.
*
* Filename: socket_server_thread.c
* Description: This file
*
* Version: 1.0.0(11/26/2021)
* Author: yanp <2405204881@qq.com>
* ChangeLog: 1, Release initial version on "11/26/2021 10:19:29 AM"
*
********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <getopt.h>
#include <pthread.h>
#include <ctype.h>
#define MSG_STR "Hello yanp\n"
#define BACKLOG 13
void printf_usage(char *program);/*打印帮助信息*/
int socket_init(char *listen_ip,int listen_port);/*socket 初始化(socket bind listen)*/
int thread_init(pthread_t *thread_id,void *(*start_routine) (void *),void *arg);
/*创建子线程并初始化线程属性*/
void *thread_worker(void *ctx);/*创建子线程之后需要处理的函数*/
int main(int argc,char **argv)
{
int daemon_run=0;
char *program;
int serv_port=0;
int listen_fd;
int clifd=-1;
int rv=-2;
int opt;
struct sockaddr_in cliaddr;
socklen_t cliaddr_len;
pthread_t tid;
struct option long_options[] =/*参数解析主要设置监听端口和是否在后台运行*/
{
{"daemon", no_argument, NULL, 'b'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
program=argv[0];
while((opt = getopt_long(argc, argv, "bp:h", long_options, NULL)) != -1)
{
switch(opt)
{
case 'b':
daemon_run=1;
break;
case'p':
serv_port = atoi(optarg);/* 将字符串转换成整型*/
break;
case 'h':
printf_usage(program);
break;
default:
break;
}
}
if(!serv_port)
{
printf_usage(argv[0]);
return -1;
}
if((listen_fd=socket_init(NULL,serv_port))<0)/*socket初始化返回一个listenfd*/
{
printf("socket_init failure error:%s",strerror(errno));
return -2;
}
if(daemon_run)/*设置在后台运行*/
{
daemon(0,0);
}
while(1)
{
printf("start accept new client income...\n");
clifd=accept(listen_fd, (struct sockaddr *)&cliaddr, &cliaddr_len);/*主线程接收新的客户端的连接*/
if(clifd < 0)
{
printf("Accept new client failure: %s\n", strerror(errno));
continue;
}
printf("Accept new client[%s:%d] successfully\n", inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
thread_init(&tid, thread_worker, &clifd);/*创建子线程并让子线程和client进行数据的收发*/
}
}
void printf_usage(char *program)
{
printf("使用方法:%s【选项】 \n", program);
printf(" %s是一个服务器程序,用来等待客户端的连接\n",program);
printf("\n传入参数\n");
printf(" -b[daemon]设置程序在后台运行\n");
printf(" -p[port ] 指定连接的端口号\n");
printf(" -h[help ] 打印帮助信息\n");
printf("\n例如: %s -b -p 8900\n", program);
return;
}
int socket_init(char *listen_ip,int listen_port)
{
int listenfd;
struct sockaddr_in servaddr;
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("socket_server to create a TCP socket fd failure:[%s]\n",strerror(errno));
return -1;
}
printf("create a tcp socket fd[%d] success\n",listenfd);
int on=1;
if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
printf("setsockopt failure:%s",strerror(errno));
return -2;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(listen_port);
if(!listen_ip)
{
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
}
else
{
servaddr.sin_addr.s_addr=htonl(listen_port);
}
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
{
printf("socket[%d] bind on port[%d] for ip address failure:%s\n",listenfd,listen_port,strerror(errno));
return -2;
}
printf("socket[%d] bind on port[%d] for ip address success\n",listenfd,listen_port);
listen(listenfd,BACKLOG);
printf("start listen on port[%d]\n",listen_port);
return listenfd;
}
int thread_init(pthread_t *thread_id,void *(*start_routine) (void *),void *arg)/*start_route是一个函数指针,指向返回值为void *类型,参数也是void类型的函数*/
{
pthread_attr_t thread_attr;
int rv = -1;
if( pthread_attr_init(&thread_attr) )/*设置线程属性*/
{
printf("pthread_attr_init() failure: %s\n", strerror(errno));
return -1;;
}
if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
{
printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
return -2;
}
if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
{
printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
return -3;
}
if( pthread_create(thread_id, &thread_attr, start_routine, arg) )/*创建子线程并执行start_routine函数*/
{
printf("Create thread failure: %s\n", strerror(errno));
return -4;
}
return 0;
}
void *thread_worker(void *ctx)/*子线程处理和客户端数据交换的处理函数*/
{
int cli_fd;
int rv;
char buf[1024];
int i;
if( !ctx )
{
printf("Invalid input arguments in %s()\n", __FUNCTION__);
pthread_exit(NULL);
}
cli_fd = *(int *)ctx;/*强制类型转换成int *类型的并把ctx的值赋值给cli_fd*/
printf("Child thread start to commuicate with socket client...\n");
while(1)
{
memset(buf, 0, sizeof(buf));
rv=read(cli_fd, buf, sizeof(buf));
if( rv < 0)
{
printf("Read data from client sockfd[%d] failure: %s and thread will exit\n",cli_fd,strerror(errno));
close(cli_fd);
pthread_exit(NULL);
}
else if( rv == 0)
{
printf("Socket[%d] get disconnected and thread will exit.\n",cli_fd);
close(cli_fd);
pthread_exit(NULL);
}
else if( rv > 0 )
{
printf("Read %d bytes data from client[%d]: %s\n", rv,cli_fd, buf);
}
/* convert letter from lowercase to uppercase */
for(i=0; i<rv; i++)/*收到client发送的数据后把它转换成大写字母并发送给客户端*/
{
buf[i]=toupper(buf[i]);
}
rv=write(cli_fd, buf, rv);
if(rv < 0)
{
printf("Write to client by sockfd[%d] failure: %s and thread will exit\n", cli_fd,strerror(errno));
close(cli_fd);
pthread_exit(NULL);
}
}
}
运行并测试:
客户端我们使用tcp test tools