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;
}