一、线程

1.1 什么是线程

  与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)。进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。线程是轻量级的进程(LWP: Light weight ProcesS) ,在Linux环境下线程的本质仍是进程。查看指定进程的 LWP号: 

ps -Lf pid

1.2 线程和进程的区别

  进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。且调用fork()来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork ()调用在时间上的开销依然不菲。线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

1.3 线程之间的共享和非共享资源

共享资源:进程ID和父进程ID;进程组ID和会话ID;用户ID和用户组ID;文件描述符表;信号处置文件;系统的相关信息:文件权限掩码(umask)、当前工作目录;虚拟地址空间(除栈、.text)

非共享资源:线程ID;信号掩码;线程特有数据;error变量;实时调度策略和优先级;栈,本地变量和函数的调用链接信息。查看Linux进程库pthread版本:

getconf GUN_LIBPTHREAD_VERSION

二、与线程有关的库函数

2.1 线程的创建

  一般情况下,main函数所在的线程称为主线程(main线程),其余创建的线程为子线程。程序默认只有一个线程。下面是一个简单的创建线程的实例:(注意,在Linux系统使用线程系统调用,都需要连接动态库pthread)

/*

    #include <pthread.h>

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                        void *(*start_routine) (void *), void *arg);
        -功能:创建一个子线程
        -参数:
            -thread:传出参数,传出创建成功的线程的id,指向新线程
            -attr:设置线程的属性,一般使用默认值传递NULL
            -start_routine:函数指针,子线程的逻辑代码
            -arg:给第三个参数使用,进行参数传递
        -返回值:
            -成功:0
            -失败:错误号,和之前的errno不相同,无法通过perror获取
                    获取错误号信息:char * strerror(int errnum);传入错误号

*/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>
void * callback(void * arg);

int main(){
    //创建一个子线程
    int num =10;
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    if(ret !=0){
        char * buf =strerror(ret);
        printf("%s\n", buf);
    }
    for (int i = 0; i < 5; i++)
    {
        printf("%d\n", i);
    }
    
    sleep(1);

    return 0;
}

void * callback(void * arg){
    printf("child thread......\n");
    printf("num= %d\n" ,*(int *)arg);
    return NULL;
}

2.2 线程的终止

/*

    #include <pthread.h>

    void pthread_exit (void *retval) ;
        -功能:终止一个线程,在哪个线程调用,就表示终止哪个线程
        -参数:
            -retval:传出参数,需要传递一个指针,作为一个返回值,可以在pthread_join()中得到

        -返回值:
            无
    pthread_t pthread_self (void) ;
        -功能:获取当前线程id
    int pthread_equal(pthread_t t1, pthread_t t2);
        -功能:比较两个线程ID是否相等
        不同的操作系统pthread_t类型的实现不一样,有无符号长整形,有结构体
*/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>


//子线程
void* callback(void* arg){
    printf("child thread id: %ld\n", pthread_self());
    return NULL;//相当于调用pthread_exit(NULL);
}
int main(){
    //创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret !=0){
        char * err = strerror(ret);
        printf("err no: %s\n", err);
    }

    //主线程
    for (int  i = 0; i < 1000; i++){
        printf("%d\n", i);
    }
    printf("main thread id: %ld, tid: %ld\n", pthread_self(), tid);
    //让主线程退出
    //退出主线程时,不会影响其他线程
    pthread_exit(NULL);
    printf("main thread exit\n");
    return 0;
}

2.3 连接已终止的线程

/*

    #include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);
        -功能:和一个已经终止的线程进行连接(回收终止线程的资源)
        -特点:阻塞(同wait),调用一次只能回收一个子线程,一般是在主线程中使用
        -参数:
            thread:需要回收的线程的ID
            retval:接收子线程退出时的返回值,注意这个值时局部变量的话无法进行返回
        -返回值:
            成功:0
            失败:错误号
*/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>
int value=10;
void* callback(void* arg){
    printf("child thread id: %ld\n", pthread_self());
    // sleep(3);
    
    pthread_exit((void*)&value);
}
int main(){
    //创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret !=0){
        char * err = strerror(ret);
        printf("err no: %s\n", err);
    }

    //主线程
    for (int  i = 0; i < 5; i++){
        printf("%d\n", i);
    }
    printf("main thread id: %ld, tid: %ld\n", pthread_self(), tid);

    //主线程调用pthread_join回收子线程的资源
    int * thread_retvalue;
    ret = pthread_join(tid, (void**)&thread_retvalue);

    if(ret !=0){
        char * err = strerror(ret);
        printf("err no: %s\n", err);
    }
    printf("exit date: %d\n", *thread_retvalue);
    printf("回收子线程资源成功\n");

    //让主线程退出
    //退出主线程时,不会影响其他线程
    pthread_exit(NULL);
    return 0;
}

其中pthread_join参数中的二级指针是为了修改主线程中的一级指针,对其进行赋值。

2.4 线程的分离

/*
    #include <pthread.h>

    int pthread_detach(pthread_t thread);
        -功能:标记一个线程为分离,当其被标记为分离时,系统在线程终止时,会自动释放资源
            注意:
                1.被分离的线程不可再次分离
                2.不可使用join进行已分离线程资源的释放
        -参数:
            -thread:线程的id
        -返回值:
            成功:0
            失败:错误号
*/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>
void* callback(void* arg){
    printf("child thread id: %ld\n", pthread_self());
    return NULL;
}

int main(){

    //创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret!=0){
        char * err = strerror(ret);
        printf("error1 :%s\n",err);
    }
    //输出主线程和子线程的id
    printf("tid : %ld, main id: %ld\n", tid, pthread_self());
    //设置分离
    
    //子线程分离后,子线程结束时资源不需要进行释放
    ret = pthread_detach(tid);
    if(ret!=0){
        char * err = strerror(ret);
        printf("error2 :%s\n",err);
    }
    //设置分离后,对分离的子线程进行连接,会报错
    ret = pthread_join(tid, NULL);
    if(ret!=0){
        char * err = strerror(ret);
        printf("error3 :%s\n",err);
    }
    pthread_exit(NULL);
    return 0;
}

2.5 线程的取消

/*

    #include <pthread.h>

    int pthread_cancel(pthread_t thread);
    -功能:取消线程(可以终止某个线程,但并非立刻终止线程,而是当线程执行到下一个取消点的时候取消)
            取消点:系统规定好的一些系统调用,可以粗略地认为用户区到内核区的切换为取消点
    -参数:
        thread:需要终止的线程id
*/

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>

void* callback(void* arg){
    printf("child thread id: %ld\n", pthread_self());
    for (int i = 0; i < 5; i++){
        printf("child :%d\n", i);
    }
    return NULL;
}

int main(){

    //创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret!=0){
        char * err = strerror(ret);
        printf("error1 :%s\n",err);
    }
    

    //取消线程
    pthread_cancel(tid);
    for (int i = 0; i < 5; i++){
        printf("main :%d\n", i);
    }
    //输出主线程和子线程的id
    printf("tid : %ld, main id: %ld\n", tid, pthread_self());
  
    pthread_exit(NULL);
    return 0;
}

2.6 设置线程的属性

更多关于线程属性的内容查阅man文档

/*

    int pthread_attr_init (pthread_attr_t *attr) ;
    -功能:初始化线程属性变量
    int pthread_attr_destroy (pthread_attr_t *attr) ;
    -功能:释放线程属性的资源
    int pthread_attr_getdetachstate (const pthread_attr_t *attr, int* detachstate) ;
    -功能:获取线程分离的状态属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int* detachstate) ;
    -功能:设置线程分离的状态属性
    -参数:
        attr:需要设置的属性变量
        detaschstate:       
                    PTHREAD_CREATE_DETACHED
                    可分离
                    PTHREAD_CREATE_JOINABLE
                    可连接


*/
//通过设置属性的方式设置线程分离
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>

void* callback(void* arg){
    printf("child thread id: %ld\n", pthread_self());
    return NULL;
}

int main(){
    //创建一个子线程
    pthread_t tid;
    //创建一个线程属性变量
    pthread_attr_t attr;
    //初始化属性变量
    pthread_attr_init(&attr);
    //设置分离状态
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    //获取线程栈的大小

    int ret = pthread_create(&tid, &attr, callback, NULL);
    if(ret!=0){
        char * err = strerror(ret);
        printf("error1 :%s\n",err);
    }
    size_t size;
    pthread_attr_getstacksize(&attr,&size);
    printf("%ld\n", size);
    //输出主线程和子线程的id
    printf("tid : %ld, main id: %ld\n", tid, pthread_self());
    
    //释放线程属性资源
    pthread_attr_destroy(&attr);
    pthread_exit(NULL);
    return 0;
}