1.基础知识
查看线程:ps -xH|grep 进程名 ;
查看进程:ps -ef |grep 进程名
不能在子线程中使用exit,否则整个进程会退出,,一般使用pthread_exit(0);
2. 线程参数的传递
//这种方式的强制转换在线程传参的时候很多
int ii = 10;
void* ptr = (void*)(long long)ii;
int jj = (int)(long long)ptr;
线程参数传递一般使用值传递,也就是使用上面的这样强制类型转换,将值(不是地址)强制转换为void *类型,注意上面的强制类型转换并没有传入地址,传入地址的方式仍然是共享变量的传入参数。
#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)
{
printf("handler1 var = %d\n", (int)(long)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)
{
printf("handler2 var = %d\n", (int)(long)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)
{
printf("handler3 var = %d\n", (int)(long)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)
{
printf("handler4 var = %d\n", (int)(long)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)
{
printf("handler5 var = %d\n", (int)(long)arg);
for(int i=0;i<5;i++)
{
sleep(1);
printf("handler5 sleep 1sec ok,%d\n",(int)(long)arg);
}
pthread_exit(0);
}
3. 线程资源的回收
3.1 线程的状态
线程有两种状态:joinable,unjoinable
joinable:当子线程终止时,不会释放线程所占用的资源,这种线程称为僵尸线程, 创建线程时线程的默认属性为joinable。
3.2 正确回收线程资源色四种方式
-
创建线程 后,调用pthread_join等待线程退出,一般不会采用这种方法,因为pthread_join会阻塞
pthread_join(pthid, NULL)
-
创建线程前,调用pthread_attr_setdetachstate将线程设为detached,这样线程退出时,系统自动回收线程资源
pthread_t pthid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // 设置线程的属性 pthread_create(&pthid, &attr, handler1, NULL);
-
创建线程后,在创建线程的程序中调用pthread_detach,将新创建的线程设置为detached状态
pthread_detach(pthid)
-
在线程函数中调用pthread_detach改变自己的状态
pthread_detach(pthread_self())
4. 线程的返回状态
int pthread_join(pthread_t thread, void **retval);
通过参数retval接受线程的返回值,具体自己设计返回值,根据返回值来做业务逻辑。
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler(void* arg);
int var=0;
/*
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg
);
RETURN VALUE
On success, pthread_create() returns 0; on error, it returns an error number,
and the contents of *thread are undefined.
*/
int main()
{
pthread_t pthid;
if(pthread_create(&pthid, NULL, handler, NULL)!=0)
{
printf("创建线程pthid1失败\n");
return -1;
}
printf("pthid=%lu\n",pthid);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret;//获取线程返回值
int result = pthread_join(pthid, (void**)&ret);
printf("子线程1已退出(result=%d,ret=%d)\n", result, ret);
return 0;
}
void* handler(void* arg)
{
//下面这两种方式都可以返回线程的值
return (void*)10;
//pthread_exit(10);
}
5. 线程取消
5.1 线程取消
- 子线程被取消后,在主线程中调用pthread_join,得到线程的返回状态是-1, PTHREAD_CANCELED
- 子线程可以调用
int pthread_setcancelstate(int state, int *oldstate);
其中state有两种状态:
PTHREAD_CANCEL_ENABLE(缺省情况)
PTHREAD_CANCEL_DISABLE
设置对pthread_cancel请求的响应方式
- 取消类型有两种:延迟取消,立即取消
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED (默认参数, 延迟到取消点取消)
PTHREAD_CANCEL_ASYNCHRONOUS(立即取消)
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
void* handler(void* arg);
int var=0;
int main()
{
pthread_t pthid;
if(pthread_create(&pthid, NULL, handler, NULL)!=0)
{
printf("创建线程pthid失败\n");
return -1;
}
usleep(2);
//取消线程
pthread_cancel(pthid);
printf("pthid = %lu\n", pthid);
printf("等待子线程退出\n");
//pthread_join会使主进程阻塞在这里,用于调试
int ret;
int result = pthread_join(pthid, (void**)&ret);
printf("子线程1已退出(result=%d,ret=%d)\n", result, ret);
return 0;
}
void* handler(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(1);//sleep函数是一个取消点,线程运行到这会取消
++var;
//设置取消点
pthread_testcancel();
//printf("handler1 sleep 1sec ok,%d\n",var);
}
printf("jj=%d\n",jj);
//子线程退出
pthread_exit(0);
}
5.2 应用场景
应用于总线程获取多个线程的状态,一旦某个线程挂掉,总线程即cancel子线程
6. 线程清理函数
6.1 概述
线程清理函数(thread cleanup function)是在线程退出时自动调用的函数。它可以用来清理线程中使用的资源,例如释放动态分配的内存、关闭文件句柄等。
线程清理函数通过pthread_cleanup_push()和pthread_cleanup_pop()函数来注册。pthread_cleanup_push()函数用于注册一个线程清理函数,它将该函数压入线程的清理函数栈中。pthread_cleanup_pop()函数则用于弹出该函数,从而使得该函数不再被调用。
6.2
- void pthread_cleanup_pop(int execute);当execute=0时,清理函数不被执行,execute=1清理函数被执行;
- 当执行pthread_exit(0),也会弹出清理函数,并且执行它;
- 线程被取消时,所有注册的清理函数被调用;
#include<stdio.h>
#include <pthread.h>
#include <unistd.h>
void* handler(void* arg);
int var=0;
void cleanfun1(void*);
void cleanfun2(void*);
void cleanfun3(void*);
int main()
{
pthread_t pthid1;
if(pthread_create(&pthid1,NULL,handler,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* handler(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);return 0;
}
pthread_cleanup_pop(1);//0不执行清理函数
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");
}
7. 线程信号
- 从外部向进程发送信号时,信号到达进程,不会中断子线程的执行
- 信号不会中断系统调用,例如sacnaf阻塞,仍然会阻塞到这里
- 捕获信号的代码放到哪里都一样,一般都放到进程的主函数中
8. 多线程版本的socket通信
server
#include "_freecplus.h"
#include <vector>
#include <pthread.h>
#include <signal.h>
void sig2or5Slot(int sig);
void threadCleanup(void* arg);
void* threadTask(void* arg);
std::vector<pthread_t> vecThreadId;
CLogFile logfile;
CTcpServer TcpServer; // 创建服务端对象
int main(int argc, char* argv[])
{
signal(2, sig2or5Slot);
signal(5, sig2or5Slot);
logfile.Open("mul.log", "a+");
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 pthid;
if (pthread_create(&pthid, NULL, threadTask, (void*)(long)TcpServer.m_connfd) != 0)
{
printf("creat pthid fail\n");
return -1;
}
vecThreadId.push_back(pthid);
}
return 0;
}
//2,15信号处理函数
void sig2or5Slot(int sig)
{
printf("sig2or5Slot begin.\n");
TcpServer.CloseListen();//关闭监听的socket
for (int i = 0; i < vecThreadId.size(); i++)
{
printf("cancel %ld\n", vecThreadId[i]);
pthread_cancel(vecThreadId[i]);//取消全部的线程,回调用线程清理函数
}
printf("sig2or5Slot end.\n");
usleep(20);
exit(0);
}
//设置线程清理函数
void threadCleanup(void* arg)
{
printf("线程清理函数开始。\n");
close((int)(long)arg);//关闭线程连接的socket
for (int i = 0; i < vecThreadId.size(); i++)
{
if (vecThreadId[i] == pthread_self())
{
vecThreadId.erase(vecThreadId.begin() + i);
}
}
printf("线程清理函数结束.\n");
}
void* threadTask(void* arg)
{
pthread_cleanup_push(threadCleanup, arg);//注册线程清理函数
pthread_detach(pthread_self()); //分离线程
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 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);
logfile.Write("接收:%s\n", strbuffer);
strcat(strbuffer, "ok"); // 在客户端的报文后加上"ok"。
printf("发送:%s\n", strbuffer);
if (TcpWrite(sockfd, strbuffer, buflen) == false) break; // 向客户端回应报文。
}
printf("客户端已断开。\n");
pthread_cleanup_pop(1);//执行线程清理函数
}
client
/*
* 程序名:demo47.cpp,此程序演示采用freecplus框架的CTcpClient类实现socket通信的客户端。
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"
int main(int argc,char *argv[])
{
if (argc!=3)
{
printf("Using:./demo47 ip port\nExample:./demo47 172.21.0.3 5005\n\n"); return -1;
}
CTcpClient TcpClient; // 创建客户端的对象。
if (TcpClient.ConnectToServer(argv[1],atoi(argv[2]))==false) // 向服务端发起连接请求。
{
printf("TcpClient.ConnectToServer(\"%s\",%s) failed.\n",argv[1],argv[2]); return -1;
}
char strbuffer[1024]; // 存放数据的缓冲区。
for (int ii=0; ii<5; ii++) // 利用循环,与服务端进行5次交互。
{
memset(strbuffer,0,sizeof(strbuffer));
snprintf(strbuffer,50,"%d:this is %d supergirl,id:%03d.",getpid(),ii+1,ii+1);
printf("receve:%s\n",strbuffer);
if (TcpClient.Write(strbuffer)==false) break; // 向服务端发送请求报文。
memset(strbuffer,0,sizeof(strbuffer));
if (TcpClient.Read(strbuffer,20)==false) break; // 接收服务端的回应报文。
printf("receve:%s\n",strbuffer);
sleep(5);
}
// 程序直接退出,析构函数会释放资源。
}