Linux下多线程服务器端的实现

本文详细介绍Linux环境下多线程编程的基本概念与实践技巧,包括线程创建、线程间同步、互斥量与信号量的使用,并通过聊天服务器实例演示多线程应用的开发流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux下多线程编程时,编译命令需要加上-lpthread选项。

g++ test.cpp -o test -lpthread

线程的创建

线程具有单独的执行流,所以会有自己的main函数。创建线程的函数如下:

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

参数含义:

thread:保存新创建线程的ID地址值。

attr:传递线程属性的参数,传递NULL表示选择默认属性。

start_routine:一个函数指针,该指针指向的函数作为线程的main函数。

arg:线程的main函数(即第三个参数)的参数信息地址值。

成功时返回0,失败时返回其他值。

示例:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;

void* threadMain(void *arg); 	//新创建线程的main函数

int main()
{
	pthread_t tId; 		//线程的id
	int threadParam(5); 	//作为线程main函数的参数
	
	if(pthread_create(&tId,NULL,threadMain,(void *)&threadParam)!=0)
	{
		cout<<"pthread_create() error!\n";
		return -1;
	}
	sleep(10);
	cout<<"end of main.\n";

	return 0;
}

void* threadMain(void *arg) 	//参数来行是void*指针,而pthread_create第四个参数的类型原本是int*指针,因此需要强制转换一下
{
	int cnt(*((int *)arg));
	for(int i=0;i<cnt;i++)
	{
		sleep(1);
		cout<<"running thread.\n";
	}
	return NULL;
}
线程的结束

上一个例子中,在主main函数中,调用了sleep(10),故意等待线程结束,但只是为了风便展示线程的创建,实际情况是不知道线程何时结束的。等待线程结束的方法是:

int pthread_join(pthread_t thread,void ** status);

参数含义:

thread:需要等待结束的线程id。

status:保存线程的main函数返回值的指针变量地址值,这是一个双重指针。

成功时返回0,失败时返回其他值。

调用该函数的进程或线程将进入阻塞状态,直到thread所指线程终止为止。

示例:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
using namespace std;

void* threadMain(void *arg);

int main()
{
	pthread_t tId;
	int threadParam(5);
	void *threadRet;

	if(pthread_create(&tId,NULL,threadMain,static_cast<void *>(&threadParam))!=0)
	{
		cout<<"pthread_create() error!\n";
		return -1;
	}

	if(pthread_join(tId,&threadRet)!=0) 	//阻塞等待线程结束,threadRet存放线程main函数的返回值的地址
	{
		cout<<"pthread_join() error!\n";
		return -1;
	}

	cout<<"Thread return message:"<<static_cast<char *>(threadRet);
	delete[] threadRet; 		//线程main函数返回的地址值是动态分配得来的,因此记得delete,不然会发生内存泄漏
	return 0;
}

void* threadMain(void *arg)
{
	int cnt(*(static_cast<int *>(arg)));
	char *msg=new char[sizeof(char)*50];
	strcpy(msg,"Hello,I'am thread~\n");

	for(int i=0;i<cnt;i++)
	{
		sleep(1);
		cout<<"running thread.\n";
	}

	return static_cast<void *>(msg);
}
线程间的同步

同一进程中多个线程是共享全局数据区和堆区域的,但是有各自的栈区域。因此线程间进行通信是很容易的,但多个线程随意访问全局数据,又会造成数据的不一致性。例子我就不举了,学过操作系统的都了解。不能同时访问的资源称做临界资源。

线程的同步有两个方面:

(1)同时访问某一内存空间。这时候需要互斥访问来保证数据的一致性。这里用到的互斥量。

(2)需要指定同一内存空间的线程执行顺序。典型的例子就是读写者问题,对于某个缓冲区,写者需要先写,然后通知读者来读。这里用到的是信号量。

互斥量

互斥量简单的理解就是一把锁。第一个访问临界资源的线程到来时,先把锁锁上,然后访问,访问完之后再解锁。因此如果在它访问的过程中,第二个线程到来了,当它想像第一个线程一样进行“上锁-->访问-->解锁”的操作时,发现锁已经锁上了,因此这时候它会阻塞等待,直到第一个线程解锁。之后到来的线程也是一样。这样就实现了互斥访问临界资源。

互斥量的创建函数和销毁函数:

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数含义:

mutex:互斥量的变量地址值。

attr:传递创建的互斥量的属性,NULL表示选择默认属性。

成功时返回0,失败时返回其他值。

上锁和解锁的函数:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

成功时返回0,失败时返回其他值。

示例:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
using namespace std;
const int NUMTHREAD=10;

void* threadInc(void *arg);
void* threadDec(void *arg);

long long num;
pthread_mutex_t mutex; 		//定义互斥量

int main()
{
	pthread_t threadId[NUMTHREAD]; 		//保存线程ID

	pthread_mutex_init(&mutex,NULL);

	for(int i=0;i<NUMTHREAD;i++) 		i为奇数时,num加上1000,偶数时减掉1000,线程互斥访问num
	{
		if(i&1) pthread_create(&(threadId[i]),NULL,threadInc,NULL);
		else pthread_create(&(threadId[i]),NULL,threadDec,NULL);
	}

	for(int i=0;i<NUMTHREAD;i++)
		pthread_join(threadId[i],NULL);

	cout<<"result:"<<num<<endl;
	pthread_mutex_destroy(&mutex);

	return 0;
}

void* threadInc(void *arg)
{
	pthread_mutex_lock(&mutex); 	//上锁
	for(int i=0;i<=1000;i++)
		num+=1;
	pthread_mutex_unlock(&mutex); 	//解锁
	return NULL;
}

void* threadDec(void *arg)
{
	pthread_mutex_lock(&mutex);
	for(int i=0;i<=1000;i++)
		num-=1;
	pthread_mutex_unlock(&mutex);
	return NULL;
}
信号量

信号量的创建和销毁函数:

int sem_init(sem_t *sem,int pshared,unsigned int value);

int sem_destroy(sem_t *sem);

参数含义:

sem:信号量变量地址值。

pshared:传递其他值时,表示可由多个进程共享这个信号量。传递0时表示只有该进程里的多个线程共享这个信号量。

value:信号量的初始值。

成功时返回0,失败时返回其他值。

处理信号量的函数:

int sem_post(sem_t *sem);

int sem_wait(sem_t *sem);

sem_post将信号量的值+1,sem_wait将信号量的值-1。调用sem_wait时,如果信号量的值为0,则将阻塞等待。

示例:

#include<iostream>
#include<cstdio>
#include<pthread.h>
#include<semaphore.h>
using namespace std;

static sem_t semOne,semTwo; 		
static int num;

void* write(void *arg); 	//写线程的main函数
void* read(void *arg); 		//读线程的main函数

int main()
{
	pthread_t id1,id2;
	sem_init(&semOne,0,0); 		//semOne初始化为0,代表缓冲区为空,读者不可读
	sem_init(&semTwo,0,1);  	//semTwo初始化为1,代表缓冲区为空,写者可以写

	pthread_create(&id1,NULL,write,NULL);
	pthread_create(&id2,NULL,read,NULL);

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);

	sem_destroy(&semOne);
	sem_destroy(&semTwo);

	return 0;
}

void* write(void *arg)
{
	for(int i=0;i<5;i++)
	{
		cout<<"Input number:";

		sem_wait(&semTwo); 	//semTwo减1,代表缓冲区不空,写者不可以写了
		cin>>num; 		//将数据写入num
		sem_post(&semOne); 	//semOne加1,代表缓冲区不空,读者可以读了
	}
	return NULL;
}

void* read(void *arg)
{
	int sum(0);
	for(int i=0;i<5;i++)
	{
		sem_wait(&semOne);
		sum+=num;
		sem_post(&semTwo);
	}
	cout<<"Result:"<<sum<<endl;
	return NULL;
}
多线程服务器端的实现

接下来就是实现多线程服务器端了,写一个简单的聊天服务器。

server.cpp:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
#include<arpa/inet.h>
using namespace std;
const int BUF_SIZE=100;
const int MAX_CLIENT=256;

int clientCnt;
int clientSocks[MAX_CLIENT];
pthread_mutex_t mutex;

void* handleClient(void *arg);
void sendMsg(char *msg,int len);
void errorHandling(char *msg);

int main()
{
	int serverSock,clientSock,port;
	sockaddr_in serverAddr,clientAddr;
	pthread_t id;
	socklen_t clientAddrSize;

	cout<<"Input port:";
	cin>>port;

	pthread_mutex_init(&mutex,NULL);
	serverSock=socket(PF_INET,SOCK_STREAM,0);

	memset(&serverAddr,0,sizeof(serverAddr));
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	serverAddr.sin_port=htons(port);

	if(bind(serverSock,(sockaddr *)&serverAddr,sizeof(serverAddr))==-1)
		errorHandling("bind() error!");
	if(listen(serverSock,5)==-1) errorHandling("listen() error!");

	while(1)
	{
		clientAddrSize=sizeof(clientAddr);
		clientSock=accept(serverSock,(sockaddr *)&clientAddr,&clientAddrSize);

		pthread_mutex_lock(&mutex);
		clientSocks[clientCnt++]=clientSock;
		pthread_mutex_unlock(&mutex);

		pthread_create(&id,NULL,handleClient,static_cast<void *>(&clientSock));
		pthread_detach(id); 		//该函数的功能和pthread_join一样,但不会进入阻塞状态。
		cout<<"Connected client IP:"<<inet_ntoa(clientAddr.sin_addr)<<endl;
	}

	close(serverSock);
	return 0;
}

void errorHandling(char *msg)
{
	cerr<<msg<<endl;
	exit(1);
}

void* handleClient(void *arg)
{
	int clientSock(*(static_cast<int *>(arg))),len;
	char msg[BUF_SIZE];

	while((len=read(clientSock,msg,sizeof(msg)))!=0)
		sendMsg(msg,len);

	pthread_mutex_lock(&mutex); 		//删除断开连接的客户端对应的套接字
	for(int i=0;i<clientCnt;i++)
	{
		if(clientSock==clientSocks[i])
		{
			for(int j=i;j<=clientCnt-2;j++)
				clientSocks[j]=clientSocks[j+1];
			break;
		}
	}	
	clientCnt--;
	pthread_mutex_unlock(&mutex);
	close(clientSock);
	return NULL;
}

void sendMsg(char *msg,int len) 		//将任何一个客户端发来的消息一一发送给全部客户端
{
	pthread_mutex_lock(&mutex);
	for(int i=0;i<clientCnt;i++)
		write(clientSocks[i],msg,len);
	pthread_mutex_unlock(&mutex);
}

client.cpp:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<pthread.h>
#include<unistd.h>
#include<arpa/inet.h>
using namespace std;
const int BUF_SIZE=100;

char name[BUF_SIZE],buf[BUF_SIZE];

void* sendMsg(void *arg);
void* recvMsg(void *arg);
void errorHandling(char *msg);

int main()
{
	int sock,port;
	sockaddr_in serverAddr;
	pthread_t sendThread,recvThread;
	void *threadReturn;
	char ip[BUF_SIZE];

	cout<<"Input ip:";
	cin>>ip;
	cout<<"Input port:";
	cin>>port;
	cout<<"Input name:";
	cin>>name;

	sock=socket(PF_INET,SOCK_STREAM,0);

	memset(&serverAddr,sizeof(serverAddr),0);
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_addr.s_addr=inet_addr(ip);
	serverAddr.sin_port=htons(port);

	if(connect(sock,(sockaddr *)&serverAddr,sizeof(serverAddr))==-1)
		errorHandling("connect() error!");

	pthread_create(&sendThread,NULL,sendMsg,static_cast<void *>(&sock));
	pthread_create(&recvThread,NULL,recvMsg,static_cast<void *>(&sock));
	pthread_join(sendThread,&threadReturn);
	pthread_join(recvThread,&threadReturn);
	
	close(sock);
	return 0;
}

void errorHandling(char *msg)
{
	cerr<<msg<<endl;
	exit(1);
}

void* sendMsg(void *arg)
{
	int sock(*(static_cast<int *>(arg)));
	char input[BUF_SIZE];

	while(1)
	{
		cin>>input;
		if(!strcmp(input,"q")||!strcmp(input,"Q"))
		{
			close(sock);
			exit(0);
		}
		strcpy(buf,name);
		strcat(buf,":");
		strcat(buf,input);
		write(sock,buf,strlen(buf));
	}
	return NULL;
}

void* recvMsg(void *arg)
{
	int sock(*(static_cast<int *>(arg)));
	char output[BUF_SIZE];
	
	while(1)
	{
		int len(read(sock,output,BUF_SIZE-1));
		if(len==-1) return (void *)-1;
		
		output[len]=0;
		cout<<output<<endl;
	}
	return NULL;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值