Linux高级系统编程-线程

进程与线程

进程 : 系统分配资源的基本单位 , 可以简单理解为一个正在进行的程序
线程 : 操作系统调度的最小单位 , 就是一段代码的执行顺序
注意:
        1,一个进程必须要有一个线程 , 该线程被称为主线程
        2,一个进程可以有多个线程 , 除主线程外的其他线程都是子线程
        3,进程被销毁时 , 其中的线程也将被销毁 .
        4,线程是轻量级的进程( LWP light weight process ),在 Linux 环境下线程的本 质仍是进程。
        5,进程所有线程都共享该进程的资源。
线程特点:
        类 Unix 系统中,早期是没有 线程 概念的, 80 年代才引入,借助进程机制实现出了 线程的概念。
因此在这类系统中,进程和线程关系密切:
        1) 线程是轻量级进程 (light-weightprocess) ,也有 PCB ,创建线程使用的底层函数和进程一样,都是 clone
        2) 从内核里看进程和线程是一样的,都有各自不同的 PCB.
        3) 进程可以蜕变成线程
        4) 在 linux 下,线程最是小的执行单位;进程是最小的分配资源单位 实际上,无论是创建进程的 fork ,还是创建线程的 pthreadcreate ,底层实现都是调 用同一个内核函数 clone
        Ø 如果复制对方的地址空间,那么就产出一个 进程
        Ø 如果共享对方的地址空间,就产生一个 线程
        Linux内核是不区分进程和线程的 , 只在用户层面上进行区分。所以,线程所有操作函数 pthread* 是库函数,而非系统调用

线程共享与非共享的资源

共享的:
        1) 文件描述符表
        2) 每种信号的处理方式
        3) 当前工作目录
        4) 用户 ID 和组 ID
        5) 内存地址空间 (.text/.data/.bss/heap/ 共享库 )
非共享的:
        1) 线程 id
        2) 处理器现场和栈指针 ( 内核栈 )
        3) 独立的栈空间 ( 用户空间栈 )
        4) errno 变量
        5) 信号屏蔽字
        6) 调度优先级

线程的优缺点

优点:
        提高程序并发性
        开销小
        数据通信、共享数据方便
缺点:
        库函数,不稳定
        调试、编写困难、gdb 不支持
        对信号支持不好 优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、 线程差别不是很大。

查看指定进程的线程号(LWP)

ps -Lf pid
pid: 进程号
注意:
        由于线程库原本不是系统本身的, 所以在链接时需要手动链接库文件编译源文件时输入
        gcc *.c -l pthread

获取当前线程号

        线程号只在它所属的进程环境中有效。
        线程号则用 pthread_t 数据类型来表示, Linux 使用无符号长整数表示。
所需头文件
        #include
函数
        pthread_t pthread_self(void);
功能:
        获取线程号。
参数:
        无
返回值:
        调用线程的线程 ID
#include 
#include 
int main(int argc, char const *argv[])
{
    pthread_t tid = pthread_self();
    printf("%ld\n",tid);
    return 0;
}

创建线程

所需头文件
        #include
函数
        int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                void *(*start_routine)(void *), void *arg );
参数:
        thread:线程标识符地址。
        attr:线程属性结构体地址,通常设置为 NULL
        start_routine:线程函数的入口地址。
        arg:传给线程函数的参数。
返回值:
        成功:0
        失败:非 0
#include 
#include 
//注意线程调用的函数返回值为任意指针类型
void *myfunc01()
{
    printf("线程%ld正在执行\n",pthread_self());
    return NULL;
}
void *myfunc02(void *arg)
{
    printf("线程%ld正在执行,参数为:%s\n",pthread_self(),(char *)arg);
    return NULL;
}
int main(int argc, char const *argv[])
{
    pthread_t p1,p2,p3;
    pthread_create(&p1,NULL,myfunc01,NULL);
    pthread_create(&p2,NULL,myfunc02,"Thread2");
    pthread_create(&p3,NULL,myfunc02,"Thread3");
    getchar();
    return 0;
}

线程的回收

        等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
        #include
        int pthread_join(pthread_t thread, void **retval);
参数:
        thread:被等待的线程号。
        retval:用来存储线程退出状态的指针的地址 , 即回收的线程调用的函数的返回值
返回值:
        成功:0
        失败:非 0
#include 
#include 
#include 
void * myfunc01(void * argv)
{
    int *time = (int *)argv;
    printf("线程A%ld将于%d秒后销毁\n",pthread_self(),*time);
    sleep(*time);
    return "线程A";
}
void * myfunc02(void * argv)
{
    int *time = (int *)argv;
    printf("线程B%ld将于%d秒后销毁\n",pthread_self(),*time);
    sleep(*time);
    return "线程B";
}
int main(int argc, char const *argv[])
{
    int n01=3,n02=5;
    pthread_t p1,p2;
    pthread_create(&p1,NULL,myfunc01,&n01);
    pthread_create(&p2,NULL,myfunc02,&n02);
    void *argv01,*argv02;
    pthread_join(p1,&argv01);
    pthread_join(p2,&argv02);
    printf("%s被回收\n",(char*)argv01);
    printf("%s被回收\n",(char*)argv02);
    return 0;
}

线程的分离

        使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的 是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
所需头文件
        #include
函数
        int pthread_detach(pthread_t thread);
参数:
        thread:线程号。
返回值 :
        成功:0
        失败:非 0
#include 
#include 
#include 
void *my_fun1(void *arg)
{
    printf("任务A将运行7s\n");
    sleep(7);
    return NULL;
}
void *my_fun2(void *arg)
{
    printf("任务B将运行3s\n");
    sleep(3);
    return NULL;
}
int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2;
    //创建一个线程(不阻塞)
    pthread_create(&tid1, NULL, my_fun1, NULL);
    pthread_create(&tid2, NULL, my_fun2, NULL);
    //分离线程(不阻塞)
    pthread_detach(tid1);
    pthread_detach(tid2);
    getchar();
    return 0;
}
案例 1: 多线程遍历字符串
#include 
#include 
#include 
#include 
void *my_fun(void *arg)
{
    char tmp[16] = "";
    strcpy(tmp, (char *)arg);
    int i = 0;
    for (i = 0; i < strlen(tmp); i++)
    {
        printf("任务%ld输出%c\n", pthread_self(), tmp[i]);
        sleep(1);
    }
    printf("任务%ld结束\n",pthread_self());
    return NULL;
}
int main(int argc, char const *argv[])
{
    //键盘输入一个字符串代表一个任务
    char buf[16] = "";
    while (1)
    {
        printf("请输入您要遍历的字符串(字符串长度要小于16位哦)");
        //得到一个任务名 启动一个线程服务
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;
        //启动线程服务
        pthread_t tid;
        pthread_create(&tid, NULL, my_fun, (void *)buf);
        pthread_detach(tid);
        if (strcmp(buf,"886") == 0)
        {
                break;
        }
    }
    return 0;
}

线程的退出

        退出当前线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
所需头文件
        #include
函数
        void pthread_exit(void *retval);
参数:
        retval:存储线程退出状态的指针。
返回值:
        无
#include 
#include 
#include 
#include 
void *my_fun(void *arg)
{
    for(int i = 0; i < 10; i++)
    {
        printf("%d\n",i);
        sleep(1);
        if (i == 3)
        {
            pthread_exit(NULL);
        }
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
        //启动线程服务
    pthread_t tid;
    pthread_create(&tid, NULL, my_fun, NULL);
    pthread_detach(tid);
    while(1);
    return 0;
}

线程的取消

退出指定线程
注意 :
        线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点( 检查点)
检测点:类似与游戏的存档,不是实时的,需要到特定的地方才会存档
所需头文件
        #include
函数
        int pthread_cancel(pthread_t thread);
参数:
        thread : 目标线程 ID
返回值:
        成功:0
        失败:出错编号
#include 
#include 
#include 
void *my_fun(void *arg)
{
    for(int i = 0; i < 10; i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
    //启动线程服务
    pthread_t tid;
    pthread_create(&tid, NULL, my_fun, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid,NULL);
    return 0;
}

线程的属性(了解)

        Linux 下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采 用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。
        如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。

线程属性结构体

typedef struct
{
        int etachstate ; // 线程的分离状态
        int schedpolicy ; // 线程调度策略
        struct sched_param schedparam ; // 线程的调度参数
        int inheritsched ; // 线程的继承性
        int scope ; // 线程的作用域
        size_t guardsize ; // 线程栈末尾的警戒缓冲区大小
        int stackaddr_set ; // 线程的栈设置
        void * stackaddr ; // 线程栈最低地址 , 即线程栈的地址 , 默认是从所属的进程的栈空间划分
        size_t stacksize ; // 线程栈的大小
} pthread_attr_t ;
注意:
        1. 线程分离状态
        2. 线程栈大小(默认平均分配)
        3. 线程栈警戒缓冲区大小(位于栈末尾)
        4. 线程栈最低地址
        以上属性的属性值不能直接设置,须使用相关函数进行操作,初始化的函数为 pthread_attr_init,这个函数必须在 pthread_create 函数之前调用。之后须用 pthread_attr_destroy 函数来释放资源。

线程属性相关函数

初始化:
作用: 初始化线程属性
        int pthread_attr_init(pthread_attr_t *attr);
返回值:
        成功:0
        失败:错误号
销毁
作用: 销毁线程属性
        int pthread_attr_destroy(pthread_attr_t *attr);
返回值:
        成功:0
        失败:错误号
分离状态:
作用 : 设置分离状态
        int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数 :
        attr:已初始化的线程属性
        detachstate:
                分离状态PTHREAD_CREATE_DETACHED(分离线程)
                PTHREAD_CREATE_JOINABLE(非分离线程)
作用 : 获取分离状态
        int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstat);
参数 :
        attr:已初始化的线程属性
        detachstate:
                分离状态PTHREAD_CREATE_DETACHED(分离线程)
                PTHREAD_CREATE_JOINABLE(非分离线程)
#include 
#include 
#include 
void *my_fun1(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("%lu 现在在运行i=%d\n", pthread_self(), i++);
        sleep(1);
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
    //定义一个线程属性变量
    pthread_attr_t attr;
    //初始化线程属性
    pthread_attr_init(&attr);
    //设置线程分离属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid1;
    //创建一个线程(不阻塞)
    pthread_create(&tid1, &attr, my_fun1, NULL);
    printf("5秒后将结束线程\n");
    sleep(5);
    pthread_cancel(tid1);
    sleep(1);
    //释放attr
    pthread_attr_destroy(&attr);
    return 0;
}

线程栈相关

// 设置栈的地址
        int pthread_attr_setstack(pthreadattrt *attr, void *stackaddr, sizet stacksize);
参数:
        attr:指向一个线程属性的指针
        stackaddr:设置的栈的地址
        stacksize:设置的栈的大小
        成功:0 ;失败:错误号
// 得到栈的地址
        int pthread_attr_getstack(pthreadattrt *attr, void **stackaddr, sizet*stacksize);
参数:
        attr:指向一个线程属性的指针
        stackaddr:返回获取的栈地址
        stacksize:返回获取的栈大小
        成功:0 ;失败:错误号
// 设置线程所使用的栈空间大小
        int pthread_attr_setstacksize(pthreadattrt *attr, sizet stacksize);
参数:
        attr:指向一个线程属性的指针
        stacksize:设置的栈大小
        成功:0 ;失败:错误号
// 得到线程所使用的栈空间大小
        int pthread_attr_getstacksize(pthreadattrt*attr, sizet *stacksize);
参数:
        attr:指向一个线程属性的指针
        stacksize:获取线程的栈大小
        成功:0 ;失败:错误号
#include 
#include 
#include 
void *my_fun1(void *arg)
{
    int i = 0;
    while (1)
    {
        printf("%lu 现在在运行i=%d\n", pthread_self(), i++);
        sleep(1);
    }
    return NULL;
}
int main(int argc, char const *argv[])
{
    //定义一个线程属性变量
    pthread_attr_t attr;
    //初始化线程属性
    pthread_attr_init(&attr);
    //设置线程分离属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    //从堆区申请空间 作为线程的栈空间
    void *stackaddr = calloc(1, 128);
    if (stackaddr == NULL)
    {
        perror("calloc");
        return 0;
    }
    //设置线程的栈的空间为堆区空间
    pthread_attr_setstack(&attr, stackaddr, 128);
    pthread_t tid1;
    //创建一个线程(不阻塞)
    pthread_create(&tid1, &attr, my_fun1, NULL);
    printf("5秒后将结束线程\n");
    sleep(5);
    pthread_cancel(tid1);
    sleep(1);
    //释放attr
    pthread_attr_destroy(&attr);
    free(stackaddr);
    return 0;
}

注意事项

        1) 主线程退出其他线程不退出,主线程应调用 pthread_exit
        2) 避免僵尸线程
                a) pthread_join
               b) pthread_detach
                c) pthread_create 指定分离属性
        被 join 的线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不 应当返回被回收线程栈中的值; 3) malloc mmap 申请的内存可以被其他线程释放
        4) 应避免在多线程模型中调用 fork ,除非马上 exec ,子进程中只有调用 fork 的线 程存在,其他线程 t 在子进程中均 pthread_exit
        5) 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

你可能感兴趣的:(Linux高级系统编程,linux,c语言)