linux之多线程

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 正确回收线程资源色四种方式

  1. 创建线程 后,调用pthread_join等待线程退出,一般不会采用这种方法,因为pthread_join会阻塞

       pthread_join(pthid, NULL)
    
  2. 创建线程前,调用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);
    
  3. 创建线程后,在创建线程的程序中调用pthread_detach,将新创建的线程设置为detached状态

    pthread_detach(pthid)
    
  4. 在线程函数中调用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 线程取消

  1. 子线程被取消后,在主线程中调用pthread_join,得到线程的返回状态是-1, PTHREAD_CANCELED
  2. 子线程可以调用
int pthread_setcancelstate(int state, int *oldstate);

其中state有两种状态:
PTHREAD_CANCEL_ENABLE(缺省情况)
PTHREAD_CANCEL_DISABLE
设置对pthread_cancel请求的响应方式

  1. 取消类型有两种:延迟取消,立即取消
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

  1. void pthread_cleanup_pop(int execute);当execute=0时,清理函数不被执行,execute=1清理函数被执行;
  2. 当执行pthread_exit(0),也会弹出清理函数,并且执行它;
  3. 线程被取消时,所有注册的清理函数被调用;
#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. 线程信号

  1. 从外部向进程发送信号时,信号到达进程,不会中断子线程的执行
  2. 信号不会中断系统调用,例如sacnaf阻塞,仍然会阻塞到这里
  3. 捕获信号的代码放到哪里都一样,一般都放到进程的主函数中

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);
  }
 
  // 程序直接退出,析构函数会释放资源。
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值