P2.多线程基本知识
- 查看线程:ps -xH|grep 进程名 ;查看进程:ps -ef |grep 进程名
- main函数为主进程/主线程,创建的线程为子线程
- 线程资源是共享的,使用相同的地址共享全局变量和对象
- 不能在子线程中使用exit,否则整个进程会退出,一般使用pthread_exit(0)
- 子线程退出尽量不要使用return,否则会报错,可以写为 return (void *)0;
代码示例:
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
int main()
{
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
if(pthread_create(&pthid2,NULL,handler2,NULL)!=0)
{
printf("创建线程pthid2失败\n");
return -1;
}
printf("pthid1=%lu,pthid2=%lu\n",pthid1,pthid2);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
pthread_join(pthid1,NULL);
printf("子线程1已退出\n");
pthread_join(pthid2,NULL);
printf("子线程2已退出\n");
return 0;
}
void* handler1(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
++var;
printf("handler1 sleep 1sec ok,%d\n",var);
}
//子线程退出
pthread_exit(0);
}
void* handler2(void* arg)
{
for(int i=0;i<30;i++)
{
sleep(1);
printf("handler2 sleep 1sec ok,%d\n",var);
}
}
运行窗口
P3.线程参数的传递
- 不能使用全局变量传参,因为线程跑起来,全局变量可能已经被改变了
- 数据类型的强制转换,用于线程的参数:指针8字节,int是4字节
int i=10; void* p=(void*)(long)i;
int jj=(int)(long)p; - 二级指针概念:
代码示例
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
void* handler3(void* arg);
void* handler4(void* arg);
void* handler5(void* arg);
long var=0;
int main()
{
pthread_t pthid1,pthid2,pthid3,pthid4,pthid5;
if(pthread_create(&pthid1,NULL,handler1,(void*)var)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
var++;
if(pthread_create(&pthid2,NULL,handler2,(void*)var)!=0)
{
printf("创建线程pthid2失败\n");
return -1;
}
var++;
if(pthread_create(&pthid3,NULL,handler3,(void*)var)!=0)
{
printf("创建线程pthid3失败\n");
return -1;
}
var++;
if(pthread_create(&pthid4,NULL,handler4,(void*)var)!=0)
{
printf("创建线程pthid4失败\n");
return -1;
}
var++;
if(pthread_create(&pthid5,NULL,handler5,(void*)var)!=0)
{
printf("创建线程pthid5失败\n");
return -1;
}
printf("pthid1=%lu,pthid2=%lu,pthid3=%lu,pthid4=%lu,pthid5=%lu\n",pthid1,pthid2,pthid3,pthid4,pthid5);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
pthread_join(pthid1,NULL);
printf("子线程1已退出\n");
pthread_join(pthid2,NULL);
printf("子线程2已退出\n");
pthread_join(pthid3,NULL);
printf("子线程3已退出\n");
pthread_join(pthid4,NULL);
printf("子线程4已退出\n");
pthread_join(pthid5,NULL);
printf("子线程5已退出\n");
return 0;
}
void* handler1(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler1 sleep 1sec ok,%d\n",(int)(long)arg);
}
//子线程退出
pthread_exit(0);
}
void* handler2(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler2 sleep 1sec ok,%d\n",(int)(long)arg);
}
pthread_exit(0);
}
void* handler3(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler3 sleep 1sec ok,%d\n",(int)(long)arg);
}
pthread_exit(0);
}
void* handler4(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler4 sleep 1sec ok,%d\n",(int)(long)arg);
}
pthread_exit(0);
}
void* handler5(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler5 sleep 1sec ok,%d\n",(int)(long)arg);
}
pthread_exit(0);
}
运行窗口
P4.线程资源的回收
- 线程有两种状态:joinable,unjoinable
joinable:当子线程主函数终止不会释放线程所占用的资源,线程为僵尸线 程,创建线程默认属性为joinable - pthread_join会在主进程中阻塞线程,一般不会使用
- 创建线程前,调用pthread_attr_setdetachstate将线程设为detached,这 样线程退出时,系统自动回收线程资源。
- 分离前pthread_join返回值为0,分离后返回值为errror=22,Invalid argument
- 创建线程后调用pthread_detach将新的线程设置为detach状态
- 在线程主调函数里面调用pthread_detach(pthread_self())
代码示例
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
{
pthread_t pthid1,pthid2;
/* pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // 设置线程的属性。*/
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0) {
printf("创建线程pthid1失败\n");
return -1;
}
//pthread_detach(pthid1);
if(pthread_create(&pthid2,NULL,handler2,NULL)!=0)
{
printf("创建线程pthid2失败\n");
return -1;
}
//pthread_detach(pthid2);
printf("pthid1=%lu,pthid2=%lu\n",pthid1,pthid2);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret=pthread_join(pthid1,NULL);
printf("子线程1已退出:%d,%s\n",ret,strerror(ret));
// pthread_join(pthid2,NULL);
printf("子线程2已退出\n");
sleep(10);
return 0;
}
void* handler1(void* arg)
{
pthread_detach(pthread_self());
for(int i=0;i<5;i++)
{
sleep(1);
++var;
printf("handler1 sleep 1sec ok,%d\n",var);
}
//子线程退出
pthread_exit(0);
}
void* handler2(void* arg)
{
pthread_detach(pthread_self());
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler2 sleep 1sec ok,%d\n",var);
}
pthread_exit(0);
}
运行窗口
P5.线程返回的状态
- int pthread_join(pthread_t thread, void **retval);
通过参数retval接受线程的返回值
根据线程返回值,来判断线程的状态
代码示例
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
int main()
{
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
printf("pthid1=%lu\n",pthid1);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret;
int result=pthread_join(pthid1,(void** )&ret);
printf("子线程1已退出(result=%d,ret=%d)\n",result,ret);
return 0;
}
void* handler1(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
++var;
printf("handler1 sleep 1sec ok,%d\n",var);
}
// return (void*)1;
//子线程退出
pthread_exit(0);
}
运行窗口
P6.线程的取消
-
int pthread_cancel(pthread_t thread);
-
调用线程取消函数,线程返回值为-1;
-
int pthread_setcancelstate(int state, int *oldstate);
其中state有两种状态:
PTHREAD_CANCEL_ENABLE(缺省情况)
PTHREAD_CANCEL_DISABLE -
取消点(Cancellation points):会产生I/O等待的函数
-
取消点设置函数:void pthread_testcancel(void);
-
取消类型有两种:延迟取消,立即取消
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED(延迟)
PTHREAD_CANCEL_ASYNCHRONOUS(立即)
代码部分(立即取消) -
应用于总线程获取多个线程的状态,一旦某个线程挂掉,总线程即cancel子线程
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
int main()
{
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
usleep(2);
pthread_cancel(pthid1); printf("pthid1=%lu\n",pthid1);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret;
int result=pthread_join(pthid1,(void** )&ret);
printf("子线程1已退出(result=%d,ret=%d)\n",result,ret);
return 0;
}
void* handler1(void* arg)
{
// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
//pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
int jj=0;
for(int i=0;i<5000000;i++)
{
;
jj++;
jj--;
jj++;
//sleep为延迟取消点
//sleep(1);
++var;
//设置取消点,
// pthread_testcancel();
//printf("handler1 sleep 1sec ok,%d\n",var);
}
printf("jj=%d\n",jj);
// return (void*)1;
//子线程退出
pthread_exit(0);
}
运行窗口
代码部分(延迟取消)
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
int main()
{
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
usleep(2);
pthread_cancel(pthid1); printf("pthid1=%lu\n",pthid1);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret;
int result=pthread_join(pthid1,(void** )&ret);
printf("子线程1已退出(result=%d,ret=%d)\n",result,ret);
return 0;
}
void* handler1(void* arg)
{
// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
// pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
int jj=0;
for(int i=0;i<5000000;i++)
{
;
jj++;
jj--;
jj++;
//sleep为延迟取消点
//sleep(1);
++var;
//设置取消点,
// pthread_testcancel();
//printf("handler1 sleep 1sec ok,%d\n",var);
}
printf("jj=%d\n",jj);
// return (void*)1;
//子线程退出
pthread_exit(0);
}
运行窗口
P7.线程清理
-
子线程退出时可能需要执行善后工作,如释放资源和锁,回滚事务等
-
善后代码写在清理函数中,不写在主线程
-
清理函数定义:void pthread_cleanup_push(void (*routine)(void *),
void *arg);在清理函数中关闭socket,将socket传给arg,将该socket进行关闭 -
void pthread_cleanup_pop(int execute);当execute=0时,清理函数不被执行,execute=1清理函数被执行
-
当执行线程取消函数,会弹出清理函数,并且执行它
-
当执行pthread_exit(0),也会弹出清理函数,并且执行它
示例代码
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
void cleanfun1(void*);
void cleanfun2(void*);
void cleanfun3(void*);
int main()
{
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
sleep(2);
pthread_cancel(pthid1);
int ret;
int result=pthread_join(pthid1,(void** )&ret);
printf("子线程已退出(result=%d,ret=%d)\n",result,ret);
return 0;
}
void* handler1(void* arg)
{
// 注册线程清理函数
int socketid=10;
pthread_cleanup_push(cleanfun1,(void*)(long)socketid);
pthread_cleanup_push(cleanfun2,NULL);
pthread_cleanup_push(cleanfun3,NULL);
for(int i=0;i<3;i++)
{
sleep(1);printf("sleep %dsec ok.\n",i+1);
}
//pthread_exit(0);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_exit(0);
}
void cleanfun1(void* arg )
{
printf("fun1 is ok,arg=%d\n",(int)(long)arg);
}
void cleanfun2(void*)
{
printf("fun2 is ok\n");
}
void cleanfun3(void*)
{
printf("fun3 is ok\n");
}
运行窗口
P8.线程与信号
- 外部使用killl命令产生的信号,通常是SIGINT等控制信号,则会遍历所有线程,直到找到一个不阻塞的线程,然后调用处理(一般从主线程找起)
- 信号并不会中断线程,主进程,线程继续处理当信号到来时
- 多个线程程序中,在某一个线程中调用signal会改变所有线程中的信号处理函数
- 主进程向子线程发送信号用pthread_kill函数,当信号处理函数和和其他线程执行完,才会继续执行中断的线程
- 不存在对不同的线程设置不同处理函数的说法,正确的做法:设置一个公共的信号处理函数,并利用pthread_kill(pthread,2)对不同的信号进行中断,如果设置没有设置信号处理函数,则整个函数退出。
代码示例
void* handler1(void* arg);
void* handler2(void* arg);
int var=0;
void hanfunc(int arg);
{
// signal(2,hanfunc);
signal(2,hanfunc);
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,handler1,NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
if(pthread_create(&pthid2,NULL,handler2,NULL)!=0)
{
printf("创建线程pthid2失败\n");
return -1;
}
sleep(1),pthread_kill(pthid2,2);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret,ists;
ret=pthread_join(pthid1,NULL);
ret=pthread_join(pthid2,NULL);
printf("子线程1已退出\n");
return 0;
}
void* handler1(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler1 sleep 1sec ok,%d\n",i);
}
//子线程退出
pthread_exit(0);
}
void* handler2(void* arg)
{
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler2 sleep 1sec ok,%d\n",i);
}
//子线程退出
pthread_exit(0);
}
void hanfunc(int arg)
{
printf("sig=%d\n",arg);
for(int j=1;j<4;j++){
printf("hanfunc j(%d)=%d\n",arg,j);
sleep(1);
}
}
运行窗口
P9.多线程程序的调试
- 查看当前运行的轻量级进程:ps -aL |grep 进程名
- 查看主进程和新进程的关系:pstree -p 主线程id
- info b查看断点信息
- info pthreads:查看当前有多少线程
- thread ID 切换到线程ID
- 只运行当前线程 set scheduler-locking on,其他线程挂起
- 线程全部运行,set scheduler-locking off
- 指定某线程执行某gdb命令:thread apply 线程id cmd
thread apply all cmd
多线程调试代码示例
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
void* pth1_main(void* arg);
void* pth2_main(void* arg);
int i=0;
int j=0;
int main()
{
pthread_t pthid1,pthid2;
if(pthread_create(&pthid1,NULL,pth1_main,(void*)0)!=0)
{
printf("pthread1_create fail\n");
return -1;
}
if(pthread_create(&pthid2,NULL,pth2_main,(void*)0)!=0)
{
printf("pthread2_create fail\n");
return -1;
}
printf("111\n");
pthread_join(pthid1,NULL);
printf("222\n");
pthread_join(pthid2,NULL);
return 0;
}
void* pth1_main(void* arg)
{
for( ;i<100;i++)
{
printf("i=%d\n",i);
sleep(1);
}
pthread_exit(NULL);
}
void* pth2_main(void* arg)
{
for(;j<100;j++)
{
printf("j=%d\n",j);
sleep(1);
}
pthread_exit(NULL);
}
P10-11.学习多线程网路服务端前的准备
- 熟悉socket通信
- 熟悉信号
- 熟悉多进程
- 了解freecplus框架
P12.搭建多线程的网络服务端框架
- 让线程去和客户端进程通信,主进程继续负责监听
- 不能使用全局变量socket,因为每连接上一个客户端,socket值会变
因此需要将socket参数在创建进程的时候就传进去。
服务端代码框架
#include "../_freecplus.h"
void* pthmain(void* arg);
{
CTcpServer TcpServer; // 创建服务端对象。
if (TcpServer.InitServer(5005)==false) // 初始化TcpServer的通信端口。
{
printf("TcpServer.InitServer(5858) failed.\n"); return -1;
}
while(true)
{
if (TcpServer.Accept()==false) // 等待客户端连接。
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
{
}
while(true)
{
if (TcpServer.Accept()==false) // 等待客户端连接。
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
{
{
{
printf("creat pthid1 fail\n");
return -1;
}
printf("TcpServer.InitServer(5858) failed.\n"); return -1;
}
while(true)
{
if (TcpServer.Accept()==false) // 等待客户端连接。
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
{
printf("creat pthid1 fail\n");
return -1;
}
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
{
printf("creat pthid1 fail\n");
return -1;
}
}
return 0;
}
void* pthmain(void* arg)
{
int sockfd=(int)(long)(arg);
while (true)
{
char strbuffer[1024]; // 存放数据的缓冲区。
int buflen=0;
memset(strbuffer,0,sizeof(strbuffer));
if (TcpRead(sockfd,strbuffer,&buflen,300)==false) break; // 接收客户端发过来的请求报文。
printf("接收:%s\n",strbuffer);
strcat(strbuffer,"ok"); // 在客户端的报文后加上"ok"。
printf("发送:%s\n",strbuffer);
if (TcpWrite(sockfd,strbuffer,buflen)==false) break; // 向客户端回应报文。
}
printf("客户端已断开。\n");
}
客户端代码框架
#include "../_freecplus.h"
int main(int argc,char *argv[])
{
CTcpClient TcpClient; // 创建客户端的对象。
if (TcpClient.ConnectToServer("192.168.74.10",5005)==false) // 向服务端发起连接请求。
{
printf("TcpClient.ConnectToServer(\"172.21.0.3\",5858) failed.\n"); return -1;
}
char strbuffer[1024]; // 存放数据的缓冲区。
for (int ii=0;ii<5;ii++) // 利用循环,与服务端进行5次交互。
{
memset(strbuffer,0,sizeof(strbuffer));
snprintf(strbuffer,50,"(%d)这是第%d个超级女生,编号%03d。",getpid(),ii+1,ii+1);
printf("发送:%s\n",strbuffer);
if (TcpClient.Write(strbuffer)==false) break; // 向服务端发送请求报文。
memset(strbuffer,0,sizeof(strbuffer));
if (TcpClient.Read(strbuffer,20)==false) break; // 接收服务端的回应报文。
printf("接收:%s\n",strbuffer);
sleep(1);
}
// 程序直接退出,析构函数会释放资源。
}
运行窗口
P13.多线程服务程序的退出
思路
- 在主进程中设置程序退出的信号2 和15的信号处理函数
- 在信号处理函数中取消主进程的监听,设置全部的线程取消
- 在线程处理函数中首先注册线程清理函数,将线程设置为分离,将取消类型设置为(立即取消、延迟取消),并且最后执行线程清理函数(注册清理函数和执行函数成对出现)
- 编写清理函数,将线程连接的socket关闭
需要注意的点:收到2,15信号,执行信号函数取消同样会调用线程清理函数,我这里usleep(200),即可发现这个问题
服务端代码
#include "../_freecplus.h"
void* pthmain(void* arg);
void mainexit(int sig);
vector<pthread_t>vpthid;
CTcpServer TcpServer; // 创建服务端对象。
int main(int argc,char *argv[])
{
signal(2,mainexit);signal(5,mainexit);
if (TcpServer.InitServer(5005)==false) // 初始化TcpServer的通信端口。
{
printf("TcpServer.InitServer(5858) failed.\n"); return -1;
}
while(true)
{
if (TcpServer.Accept()==false) // 等待客户端连接。
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,pthmain,(void*)(long)TcpServer.m_connfd)!=0)
{
printf("creat pthid1 fail\n");
return -1;
}
vpthid.push_back(pthid1);
}
return 0;
}
//2,15信号处理函数
void mainexit(int sig)
{
printf("mainexit begin.\n");
TcpServer.CloseListen();//关闭监听的socket
for(int i=0;i<vpthid.size();i++)
{
printf("cancel %ld\n",vpthid[i]);
pthread_cancel(vpthid[i]);//取消全部的线程
}
printf("mainexit end.\n");
usleep(20);
exit(0);
}
//设置线程清理函数
void pthmainexit(void* arg)
{
printf("线程清理函数开始。\n");
close((int)(long)arg);//关闭线程连接的socket
for(int i=0;i<vpthid.size();i++)
{
if(vpthid[i]==pthread_self())
{
vpthid.erase(vpthid.begin()+i);
}
}
printf("线程清理函数结束.\n");
}
void* pthmain(void* arg)
{
pthread_cleanup_push(pthmainexit,arg);//注册线程清理函数
pthread_detach(pthread_self());
pthread_setcanceltype(PTHREAD_CANCEL_DISABLE,NULL);
int sockfd=(int)(long)(arg);
while (true)
{
char strbuffer[1024]; // 存放数据的缓冲区。
int buflen=0;
memset(strbuffer,0,sizeof(strbuffer));
if (TcpRead(sockfd,strbuffer,&buflen,300)==false) break; // 接收客户端发过来的请求报文。
printf("接收:%s\n",strbuffer);
strcat(strbuffer,"ok"); // 在客户端的报文后加上"ok"。
printf("发送:%s\n",strbuffer);
if (TcpWrite(sockfd,strbuffer,buflen)==false) break; // 向客户端回应报文。
}
printf("客户端已断开。\n");
pthread_cleanup_pop(1);//执行线程清理函数
}
运行窗口