linux高级编程笔记(七)——线程

目录

一、线程概念

1 线程和进程的关系

2 线程间共享资源 

3 线程非共享资源 

4 优缺点

二、进程控制原语

1 pthread_create函数

2 pthread_self函数

3 pthread_exit函数

4 pthread_join函数

5 pthread_detach函数

6 pthread_cancel函数

7 pthread_equal函数

三、线程属性

1 线程属性初始化

2 线程的分离状态

3 线程的栈空间

四、NPTL

五、线程使用注意事项


一、线程概念

1 线程和进程的关系

  • 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
  • 从内核里看进程和线程是一样的,都有各自不同的PCB,但是同一进程的各个线程PCB中指向内存资源的三级页表是相同的
  • 进程可以蜕变成线程
  • 在美国人眼里,线程就是寄存器和栈
  • 在linux下,线程最是小的执行单位;进程是最小的分配资源单位

查看进程的LWP(线程号)

ps -Lf pid
ps -eLf

2 线程间共享资源 

  • 文件描述符表
  • 每种信号的处理方式(减少多线程程序中使用信号)
  • 当前工作目录
  • 用户ID和组ID
  • 内存地址空间(.text/.data/.bss/heap/共享库)

3 线程非共享资源 

  • 线程id
  • 处理器现场和栈指针(内核栈)
  • 独立的栈空间(用户空间栈)
  • errno变量
  • 信号屏蔽字
  • 调度优先级 

4 优缺点

优点:

  • 提高程序的并发性
  • 开销小,不用重新分配内存
  • 通信和共享数据方便

 缺点:

  • 线程不稳定(库函数实现)
  • 线程调试比较困难(gdb支持不好)
  • 对信号支持不好

二、进程控制原语

1 pthread_create函数

 #include <pthread.h>
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

描述:

创建一个线程

参数:

thread 传出参数,获得当前创建的线程的唯一标识

attr 线程属性设置,如使用默认属性,则传NULL

start_routine 函数指针,指向新线程应该加载执行的函数模块

arg 指定线程将要加载调用的那个函数的参数

返回:

如果成功,pthread_create()返回0;发生错误时,它返回一个错误号,*thread的内容是未定义的

2 pthread_self函数

#include <pthread.h>
pthread_t pthread_self(void);

描述:

这个函数总是成功的,返回调用线程的ID。

注意:

  • 线程 ID为pthread_t类型,在 Linux 下为无符号整数(%lu),其他系统中可能是结构体实现。
  • 线程ID是进程内部,识别标志(两个进程间,线程ID允许相同)。在进程间应该用LWP号来区别不同的线程。
  • 不应使用全局变量pthread_t tid,在子线程中通过 pthread_create传出参数来获取线程ID,而应使用pthread_self。

示例代码 :

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>//exit
#include <string.h>//stderr

void * tfn(void *arg)
{
        printf("tfn -- pid = %d, tid = %lu\n",getpid(),pthread_self());

        return (void*)0;
}

int main()
{
        pthread_t tid;
        printf("main -- pid = %d, tid = %lu\n",getpid(),pthread_self());

        int ret = pthread_create(&tid,NULL,tfn,NULL);

        if(ret!=0)
        {
                fprintf(stderr,"pthread create error: %s\n",strerror(ret));
                exit(1);
        }

        sleep(1);

        return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <error.h>
#include <stdlib.h>//exit
#include <string.h>//stderr

void * tfn(void *arg)
{

        int i = (int) arg;

        printf("#%d tfn -- pid = %d, tid = %lu\n",i,getpid(),pthread_self());

        return (void*)0;
}

int main()
{
        pthread_t tid;
        printf("main -- pid = %d, tid = %lu\n",getpid(),pthread_self());

        int i;
        
        for(i=0;i<5;i++)
        {
            int ret = pthread_create(&tid,NULL,tfn,(void *)i);
    
            if(ret!=0)
            {
                fprintf(stderr,"pthread create error: %s\n",strerror(ret));
                exit(1);
            }
        }      

        return 0;
}

3 pthread_exit函数

在多进程程序中,可以使用exit退出。而在多线程下,使用exit会将整个进程退出,导致一些没有运行完毕的线程都结束。即使使用return只会退出当前的函数,也无法正确退出。因此,在多线程中,应该使用pthread_exit函数。在之前的代码示例中,main函数退出时,会退出整个进程,因此,未执行完毕的线程也都退出。因此,应该使用pthread_exit退出主线程,这样子线程将会继续执行完毕。

#include <pthread.h>
void pthread_exit(void *retval);

4 pthread_join函数

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);//回收线程,retval为线程的返回值。成功返回0,失败返回-1。

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

typedef struct{
        int var;
        char str[64];
} exit_t;

void * tfn(void *arg)
{
        exit_t * ret;
        ret = malloc(sizeof(exit_t));
        ret->var = 77;
        strcpy(ret->str,"hello world");
        return (void*) ret;
}

int main()
{
        pthread_t tid;
        exit_t * ret;

        pthread_create(&tid,NULL,tfn,NULL);
        pthread_join(tid,(void**)&ret);//回收线程

        printf("pthread var = %d, str = %s\n",ret->var,ret->str);

        return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

typedef struct{
        int var;
        char str[64];
} exit_t;

void * tfn(void *arg)
{
        exit_t * ret;
        ret = malloc(sizeof(exit_t));
        ret->var = (int) arg;
        strcpy(ret->str,"hello world");
        return (void*) ret;
}

int main()
{
        pthread_t tid[5];
        exit_t * ret[5];
        int i;

        for(i=0;i<5;i++)
        {
            pthread_create(&tid[i],NULL,tfn,(void *) i);

        }

        for(i=0;i<5;i++)
        {
            pthread_join(tid[i],(void**)&ret[i]);//回收线程,回收完毕才会往下执行
            printf("pthread var = %d, str = %s\n",ret[i]->var,ret[i]->str);
        }

        return 0;
}

5 pthread_detach函数

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

void * tfn(void *arg)
{
        int n = 5;
        while(n--)
        {
                printf("pthread tfn n = %d\n",n);
                sleep(1);
        }
        return (void*) 7;
}

int main()
{
        pthread_t tid;
        void * ret;
        
        pthread_create(&tid,NULL,tfn,NULL);
        pthread_detach(tid);//将线程分离出来,不需要父进程回收

        int retval = pthread_join(tid,(void**) &ret);

        if(retval != 0)//由于子线程已经分离出来了。因此,会报错
        {
                fprintf(stderr,"pthread_join error %s\n", strerror(retval));
        }
        else
        {
                printf("pthread exit with %d\n",(int)ret);
        }

        return 0;
}

6 pthread_cancel函数

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

void * tfn1(void *arg)
{
        printf("thread 1 exiting\n");
        return (void*) 111;
}

void * tfn2(void *arg)
{
        printf("thread 2 exiting\n");
        return (void*) 222;
}

void * tfn3(void *arg)
{

        while(1)
        {
                printf("thread 3. I'm going to die...\n");
                sleep(1);
        }
}

int main()
{
        pthread_t tid;
        void * ret;
        int i;

        pthread_create(&tid,NULL,tfn1,NULL);
        pthread_join(tid,&ret);
        printf("thread 1 exit code = %d\n",(int)ret);

        pthread_create(&tid,NULL,tfn2,NULL);
        pthread_join(tid,&ret);
        printf("thread 2 exit code = %d\n",(int)ret);

        pthread_create(&tid,NULL,tfn3,NULL);
        sleep(3);

        //杀死子线程,需要线程有取消点(系统调用等),由于tfn3中有printf,该函数会调用write,
        //所以可以取消,如果去掉有系统调用的逻辑,该线程不会被取消
        pthread_cancel(tid);
        pthread_join(tid,&ret);
        printf("thread 3 exit code = %d\n",(int)ret);//线程3由于已经被杀死了,ret此时为-1

        return 0;
}

7 pthread_equal函数

int pthread_equal(pthread_t t1, pthread_t t2);//比较两个线程是否相等

三、线程属性

typedef struct
{
    int etachstate; //线程的分离状态,可以实现与pthread_detach一样的功能
    int schedpolicy; //线程调度策略
    structsched_param schedparam; //线程的调度参数
    int inheritsched; //线程的继承性
    int scope; //线程的作用域
    size_t guardsize; //线程栈末尾的警戒缓冲区大小
    int stackaddr_set; //线程的栈设置
    void* stackaddr; //线程栈的位置。栈的低地址
    size_t stacksize; //线程栈的大小。默认为每一个线程平均分配栈空间(8M)
}pthread_attr_t;

1 线程属性初始化

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_initI这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。

线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

2 线程的分离状态

#include <pthread.h>

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

参数:

attr 已初始化的线程属性

detachstate PTHREAD_CREATE_DETACHED(分离线程)

                    PTHREAD_CREATE_JOINABLE(非分离线程)

代码示例:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h> //%s

void * tfn(void * arg)
{
        while(1)
        {
                printf("thread - tfn - ");
                sleep(1);
        }
}

int main()
{
        pthread_t tid;
        void * tret;

        pthread_attr_t attr;

        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

        pthread_create(&tid,&attr,tfn,NULL);
        pthread_attr_destroy(&attr);

        int ret;
        while(1)
        {
                ret = pthread_join(tid,(void **)&tret);
                if(ret !=0 )
                {
                        fprintf(stderr,"thread %s\n", strerror(ret));
                }
                else
                {
                        printf("thread - main -\n");
                }
                sleep(1);
        }

        return 0;
}

3 线程的栈空间

#include <pthread.h>

int pthread_attr_setstack(pthread_attr_t *attr,
                                 void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr,
                                 void **stackaddr, size_t *stacksize);

参数:

attr 指向一个线程属性的指针

stackaddr 返回获取的栈地址

stacksize 返回获取的栈大小

代码示例:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 0x10000

int print_ntimes(char *str)
{
    sleep(1);
    printf("%s\n", str);
    return 0;
}

void *th_fun(void *arg)
{
    int n = 3;
    while (n--) print_ntimes("hello th_fun\n");
}

int main(void)
{
    pthread_t tid;
    int err, detachstate, i = 1;
    pthread_attr_t attr;
    size_t stacksize;
    void *stackaddr;
    pthread_attr_init(&attr);
    pthread_attr_getstack(&attr, &stackaddr, &stacksize);
    printf("stackadd=%p\n", stackaddr);
    printf("stacksize=%x\n", (int)stacksize);
    pthread_attr_getdetachstate(&attr, &detachstate);

    if (detachstate == PTHREAD_CREATE_DETACHED)
        printf("thread detached\n");
    else if (detachstate == PTHREAD_CREATE_JOINABLE)
        printf("thread join\n");
    else
        printf("thread un known\n");

    /* 设置线程分离属性*/
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    while (1) {
        /* 在堆上申请内存,指定线程栈的起始地址和大小*/
        stackaddr = malloc(SIZE);
        if (stackaddr == NULL) {
            perror("malloc");
            exit(1);
        }

        stacksize = SIZE;
        pthread_attr_setstack(&attr, stackaddr, stacksize);
        err = pthread_create(&tid, &attr, th_fun, NULL);
        if (err != 0) {
            printf("%s\n", strerror(err));
            exit(1);
        }
        printf("%d\n", i++);
    }

    pthread_attr_destroy(&attr);
    
    return 0;
}

四、NPTL

查看当前pthread库版本 getconf GNU_LIBPTHREADED_VERSION

五、线程使用注意事项

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值