多线程编程1:线程创建与线程状态

一、进程、线程之间的关系

  • 线程是轻量级进程,进程是资源分配的最小单位,线程是操作系统调度执行的最小单位;

  • 进程有自己独立的虚拟地址空间,不同进程之间用户空间相互独立,内核空间共享

    • 32位系统,进程创建之后,分配的虚拟地址空间是0~ 2 32 2^{32} 232-1(linux4G:3G用户+1G内核),虽然说每个进程用户空间都是独立的,但也都是虚拟地址空间,所以说,不同进程的变量虚拟地址可能相同,只是最后映射到不同的物理内存
    • 如果不同进程的变量虚拟地址空间相同的话,绝大数情况下映射的物理空间不一样,但是有一种内存共享技术,内存共享数据要求映射的物理地址是一样的
  • 相同进程的不同线程

    • 相同进程中的多个线程共享同一个地址空间
    • 在一个地址空间中多个线程独享: 每个线程都有自己的栈区,寄存器等(上下文信息)
    • 在一个地址空间中多个线程共享: 代码段,堆区,全局数据区,文件描述符都是线程共享的
  • 一个进程至少有一个线程(主线程):(一般主线程退出,进程 结束,工作线程也会退出)

    • windows下,主线程退出,工作线程也会退出,也就是说主线程结束,进程也就结束了;
    • Linux中,如果主线程退出,工作线程一般不会受影响(windows下的wsl),还会继续运行下去,这个时候这个进程就变成了僵尸进程,但是在有一些linux版本中,主线程退出也会导致工作线程退出(虚拟机下的centos);
    • 如果进程中某个线程崩溃,会对其他线程造成影响吗?
      • 一般来说,每个线程都会独立执行的单位,都有自己的上下文堆栈,一个线程的崩溃是不会对其他线程产生影响的;
      • 但是通常情况下,线程崩溃会触发产生了进程内的错误,Linux中会产生segment fault错误,该信号,OS默认的处理方法是结束进程,进程中的其他线程也就不存在了;
  • 主线程和子线程的关系: joinableunjoinable(detach)

    • joinable : 线程结束的时候,不会自己释放资源,需要主线程pthread_join之后才会释放,如果主线程通过pthread_exit退出时,并没有pthread_join,导致子线程结束,资源会得不到回收。
    • unjoinable:通过pthread_detach 和主线程分离,线程结束退出的时候会自己释放资源,这个时候主线程不可以pthread_join

    TIP:

    • 就算子线程detach了,主线程结束,进程结束,子线程还是会结束,只是不需要主线程join而已,想让子线程不结束,需要通过pthread_exit主进程
    • detach出去的子线程结束,主线程还没结束,进程不会结束

二、线程创建

  1. Linux

    函数标签:

    #include<pthread>
    #include<unistd.h>
    #include<stdio.h>
    int pthread_create(pthread_t *thread,  
    				   const pthread_attr_t *attr,
                       void *(*start_routine) (void *), 
                       void *arg);
     **参数**
     	参数一:输出参数,如果线程创建成功,通过这个参数可以得到创建成功的线程ID
     	参数二:指定线程的属性,一般设置为NULL,表示使用默认属性
     	参数三:start_routine指针 指定线程函数,输入参数是void*,输出参数也是void
     	参数四:传入线程函数的参数,由于是void*,可以方便最大化的传入信息
     **返回值**:创建成功返回0,失败返回响应的错误码,常见的错误码有EAGAIN、EINVAL和EPERM。
     			EAGAIN:没有足够的资源创建线程,或者是线程数量达到上线(进程/内核);
     			EINVAL:无效线程属性。
    

    TIP: 函数指针指定的线程函数必须是**_cdecl调用**,与windows不同**(_stdcall)**,而C/C++中默认的函数调用方式就是_cdecl调用。

  2. windows
    函数标签一,系统API:

    #include<windows.h>
    #include<stdio.h>
    HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES   lpThreadAttributes,  安全属性,指定为NULL
      SIZE_T                  dwStackSize,         线程栈空间大小,0就是使用默认大小
      LPTHREAD_START_ROUTINE  lpStartAddress,      线程函数
      LPVOID                  lpParameter,         线程参数
      DWORD(无符号整型)         dwCreationFlags,    设置为0,表示线程创建好之后函数立即启动
      LPDWORD                 lpThreadId          [out]线程ID
    );
    //参数补充
    1. typedef void* LPVOID;
    2. dwCreationFlags类型是无符号整型,一般设置为0,创建好立即启动。如果设置为4,
    	对应宏CREATE_SUSPENDED,可以再需要的时候通过**ResumeThread** API让线程运行起来
    3. lpThreadId 是线程创建成功返回的线程ID,是这也是一个 32 位
    	无符号整数(DWORD)的指针(LPDWORD)
    //返回值
    成功返回句柄(HANDLE类型)管理线程对象,失败返回NULL

    TIP:
    1. 如果栈里需要分配比较大的内存,建议使用堆空间
    2. 线程函数(LPTHREAD_START_ROUTINE )类型定义(__stdcall )
    typedef DWORD ( __stdcall *LPTHREAD_START_ROUTINE )(LPVOID lpThreadParameter);
    3. windows定义__stdcall 的宏有:WINAPICALLBACK

    函数标签二,CRT提供的线程创建函数:

    #include<process.h>
    #include<stdio.h>
    uintptr_t _beginthreadex( 
       void *security,  
       unsigned stack_size,  
       unsigned ( __stdcall *start_address )( void * ),  
       void *arglist,  
       unsigned initflag,  
       unsigned *thrdaddr   
    );  
    参数列表和注意事项基本上和CreateThread基本一致,_beginthreadex底层调用的是CreateThread 
    
  3. C++11
    优点:

    • 无论是 Linux 还是 Windows 上创建线程的 API,都有一个非常不方便的地方,就是线程函数的签名必须是固定的格式**(参数个数和类型、返回值类型都有要求)。
    • C++11 新标准引入了一个新的类 std::thread(需要包含头文件<thread>),使用这个类可以将任何签名形式的函数作为线程函数,thread t(func,arg1,arg2,arg3....)
    • linux的pthread_create()只能指定,类的静态成员函数作为入口函数,不能指定不同成员函数作为入口函数,因为普通成员函数多了一个类对象的this指针,pthread_create()传入的函数只能接收一个参数,C++11的thread就很好的解决了这个问题。

    TIP: 需要保证线程函数运行过程中,std::thread线程对象必须有效

  4. 获取线程ID

    • Linux:获取线程id的三种方法

      • 创建线程的时候

        #inclide<pthread.h>
        pthread_t tid;
        pthread_create(&tid,NULL,thread_func,NULL);
        
      • 在线程中调用pthread_self()函数获取

        #include<pthread.h>
        pthread_t tid = pthread_self(); 
        //和方法一相同,本质上一块内存地址,这些ID在全系统中可能不是唯一的,且数值比较大
        
      • 通过系统调用获线程ID

        #include <sys/syscall.h>
        #include <unistd.h>
        
        int tid = syscall(SYS_gettid);
        // 这个获取的ID是系统范围内全局唯一的饿,数值一般不会太大
        
    • Windows:DWORD GetCurrentThreadID();

    • C++11:两种方法

      • 命令空间std::this_thread::get_id();
      • 类的成员方法t.get_id();
      • 但是他们返回的都是std::thread::id对象,一般只能通过cout输出,不可以强转为整型;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值