Linux--线程控制及线程id的探索

1 线程控制

1.1 线程的创建

1.通过pthread_create函数来创建线程
(1)函数原型:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

(2)参数解析:
①thread为线程id(是输出型参数,表示线程创建之后通过thread参数可以拿到线程的线程id);
attr表示线程的属性,默认可设为NULL;
start_routine为线程处理函数;
arg为线程处理函数的参数(表示对线程编号为thread的线程进行操作)。
②可以看到:线程处理函数的参数及返回值都是void*,因为C语言没有泛型编程,而线程处理的类型不一定相同,所以用void*接收任意类型,达到泛型编程的目的。

注意:在Linux里没有真正的线程,所以需要使用第三方的库,主要采用POSIX线程库,则使用线程相关函数必须包含头文件

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

void* thread_entry(void* arg)
{                                                                                                                  
    printf("thread\n");
    return NULL;
}
int main()
{
    pthread_t id;
    pthread_create(&id,NULL,thread_entry,NULL);
    sleep(1);   //主线程必须要等1秒,否则如果主线程先被调度,就会导致主线程结束后,新线程还没有执行。
    return 0;
}

例2:查看线程的线程id(通过pthread_self( )函数,可以查看自己的线程id)

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

void* thread_entry(void* arg)
{
    printf("child thread, id=%d\n",pthread_self());
    return NULL;
}
int main()
{
    pthread_t id;
    pthread_create(&id,NULL,thread_entry,NULL);
    sleep(1);
    printf("mian thread, child thread id:%d\n",id);
    return 0;
}

运行结果:(可以看到:在mian函数里面定义了一个pthread_t类型的变量,通过pthread_create函数,可以拿到新线程的线程id,这个值与新线程里面调用pthread_self函数得到的线程id的值相同)

[root@localhost 线程创建]# gcc -o thread2 thread2.c -lpthread
[root@localhost 线程创建]# ./thread2
child thread, id=-1216566416
mian thread, child thread id:-1216566416

3.当创建一个新线程后,新线程去执行相应的处理函数,主线程继续往下执行。
4.创建的新线程和主线程谁先执行并不确定,由调度器决定,(因为线程是轻量级进程,父子进程谁先执行并不确定)

1.2 线程等待

1 为什么线程需要等待
(1)需要知道线程退出信息;
(2)需要知道线程运行结果是否正确;
(3)回收线程的资源;
(4)如果不等待就会造成和僵尸进程类似的问题(注意没有僵尸线程);
2 线程等待,只有当线正常退出才能被join
(1)线程正常退出:代码执行完结果正确;代码执行完结果不正确;
(2)因为如果线程异常退出,整个进程就会被终止掉。
3.线程等待函数(pthread_join)

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval); 
//thread表示需要等待线程的线程id,retval为thread线程执行其线程处理函数的返回值(retval为输出型参数,为了拿到线程函数的返回值),因为线程处理函数的返回值为void*,所以需要用void**来接收返回值

4.通过pthread_join( )函数,线程等待以阻塞方式等待,没有非阻塞等待方式。而进程等待有阻塞和非阻塞形式。
调用pthread_join函数的线程会被挂起等待,直到被等待的进程终止才继续执行。
5.使用示例:

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

void* thread_run(void* arg)
{
    printf("im thread1\n");
    return (void*)5;
}
int main()
{
    pthread_t pid;
    pthread_create(&pid,NULL,thread_run,NULL);

    void* ret;
    pthread_join(pid,&ret);      //将线程处理函数的返回值通过ret保存

    printf("%d\n",(int)ret);
    return 0;
}

运行结果:(可以看到pid线程的返回值为5,主线程等待该线程后,拿到的返回值为5)
这里写图片描述

1.3 线程的终止

1.3.1 线程终止有三种方法

(1)线程处理函数里return;
(2)pthread_exit(主线程调用该函数,我们无法获得主线程的退出信息,主线程表示的是进程的退出必须采用进程的退出方式);

#include <pthread.h>
void pthread_exit(void *retval);
//在线程处理函数里面调用该函数,用来终止该线程,并且线程的退出码保存在retval里

示例1:(在thread_run函数里面调用pthread_exit函数,函数的参数为线程退出码,这里将线程退出码设为123)

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

void* thread_run(void* arg)                                                                                                             
{
    printf("Im thread1\n");
    pthread_exit((void*)123);
}
int main()
{
    pthread_t pid;
    pthread_create(&pid,NULL,thread_run,NULL);
    void* ret;
    pthread_join(pid,&ret);
    printf("thread1 exit code:%d\n",(int)ret);
    return 0;
}

运行结果:(可以看到:通过线程等待,通过ret拿到的线程退出码就是123)
这里写图片描述
示例2:主线程调用pthread_exit函数

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

void* thread_run(void* arg)
{
    printf("thread 1\n");
}
int main()
{
    pthread_t pid;
    pthread_create(&pid,NULL,thread_run,NULL);
    sleep(1);
    printf("main thread\n");                                                                                                            
    pthread_exit((void*)456);
    return 0;
}

运行结果:(可以看到主线程调用pthread_exit函数,当参数设为456时,进程的退出码并不是456而是0)
这里写图片描述
总结:主线程的退出必须采用进程的退出方式。
(3)pthread_cancel(取消某个线程,也可以自己取消自己)

      #include <pthread.h>

       int pthread_cancel(pthread_t thread);

示例:

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

void* thread_run(void* arg)
{
    printf("Im thread1\n");
    pthread_exit((void*)123);
}
int main()
{
    pthread_t pid;
    pthread_create(&pid,NULL,thread_run,NULL);
    pthread_cancel(pid);

    void* ret;
    pthread_join(pid,&ret);
    printf("thread1 exit code:%d\n",(int)ret);                                                                                          
    return 0;
}

运行结果:(可以看到:用pthread_cancel后,线程的退出码为-1,因为这是宏PTHREAD_CANCELED的值。
这里写图片描述
如果主线程在取消线程之前sleep几秒,则那个线程就会正常退出,退出码就是其函数返回值
这里写图片描述
2.注意在线程处理函数中调用exit,直接导致进程退出
示例:在线程处理函数thread_run函数里调用exit:

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

void* thread_run(void* arg)
{
    printf("Im thread1\n");
    exit(123);                                                                                                                          
}
int main()
{
    pthread_t pid;
    pthread_create(&pid,NULL,thread_run,NULL);
    void* ret;
    pthread_join(pid,&ret);
    printf("thread1 exit code:%d\n",(int)ret);
    return 0;
}

运行结果:(可以看到在thread_run里面调用exit,将不会输出”thread1 exit code:“,因为直接导致进程退出,则主线程也不会下继续执行下面的代码,也就不会打印那句话,并且通过echo $?可以查看进程退出码为123,也就是exit里面的哪个参数。
这里写图片描述

1.4 线程的分离

1.因为线程需要被等待,如果不等待就会造成内存泄露。如果不想知道线程的退出信息,可以将线程设为分离状态。
2.相关函数:

pthread_detach(pthread_t thread);

3.使用示例:
例1:

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

void* entry(void* arg)
{
    printf("thread id:%d\n",pthread_self());
    sleep(5);

    return (void*)3;
}
int main()
{
    pthread_t id;
    pthread_create(&id,NULL,entry,NULL);
   // pthread_detach(id);
    sleep(1);                                                                                                                           

    void* ret;
    pthread_join(id,&ret);
    printf("thread id %d ,return %d\n",id,(int)ret);
    return 0;
}

运行结果:
①如果没有pthread_detach这句,运行结果如下:

[root@localhost 线程终止]# ./threaddt
thread id:-1216820368
thread id -1216820368 ,return 3

②如果加上pthread_detach这句,运行结果如下:(可以看到输出的线程函数的返回值是一个随机数,说明已经无效,不能对一个已经分离的线程进行线程等待)

[root@localhost 线程终止]# ./threaddt
thread id:-1216951440
thread id -1216951440 ,return 134514203

例2:将线程设为分离后,再通过pthread_join等待,通过函数返回值进行进一步验证(被分离后不能再等待)

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

void* entry(void* arg)
{
    pthread_detach(pthread_self());
    printf("new thread\n");
    return NULL;
}
int main()
{
    pthread_t id;
    pthread_create(&id,NULL,entry,NULL);
    sleep(1);  //保证新线程先执行

    int ret = pthread_join(id,NULL);
    if(ret == 0)
    {   
        //说明等待成功
        printf("wait success\n");
    }   
    else
    {
        printf("wait failed\n");
    }
    return 0;
}             

运行结果:

[root@localhost 线程终止]# ./threaddt2
new thread
wait failed   //输出wait failed,说明等待失败

4.注意点:

* 设为分离状态的线程不需要等待,也不能被其它线程回收或杀死,当其运行结束后会自动释放自己的资源。
* 线程被分离,则它的退出信息我们不再关心,退出结果我们也不再关心,资源我们也无需再回收。
* 但是当一个被分离的线程出错后也会影响到其它线程,因为无论怎样,在、线程都是属于进程内部的。

5.线程不仅可以被其它线程设为分离状态,也可以自己把自己设为分离状态:

pthread_detach(pthread_self());   //自己将自己设为分离状态

2 探索线程id

1.在Linux下,线程是通过进程模拟的,每个线程就会有一个PCB;
没有线程之前,每个进程对应一个PCB,有了线程之后就会使得一个进程管理多个PCB,但是为了使得同一个进程里面的线程对应相同的进程id,所以每个PCB里面不仅需要线程id还要有标识进程的id。
2.在Linux下,一个进程就对应多个PCB,一个线程对应一个PCB;Linux内核引入了线程组的概念。则对于多线程的进程就是一个线程组,在这个线程组里面,每个线程的线程id不相同,但是他们的组id相同,这个组id就是进程id。在线程的PCB里面还包含一个指向组长线程的指针,这个组长就是主线程;主线程的线程id与进程id相同。
3.先有进程然后才会有线程。(因为进程是进行资源分配的最小单位)
4.tast_struct结构体

struct task_struct {
        ...
        pid_t pid;    //这里的pid就是线程id
        pid_t tgid;   //tgid表示线程组id,也就是进程id
         ... 
        struct task_struct *group_leader;     //组长线程,也就是主线程
           ... 
        struct list_head thread_group;      //使用一个链表,将同一个线程组里面的线程连接起来,list_head就是这个链表的头结点
        ... 
}; 

5.通过命令查看线程id(通过ps -eLF)

[root@localhost 线程创建]# ps -eLF |head -n 1 &&  ps -eLF | grep thread2 |grep -v grep
UID        PID    PPID    LWP   C  NLWP    SZ   RSS  PSR  STIME  TTY          TIME     CMD
root     11032  3339  11032  0    2    3088     480   0  19:35      pts/0     00:00:00  ./thread2
root     11032  3339  11033  0    2    3088     480   0  19:35      pts/0     00:00:00   ./thread2

分析:
PID表示进程id,PPID表示父进程id,LWP表示轻量级进程id,也就是线程id,后面的CMD表示属于哪一个进程。
可以看到:

* 对于进程./thread2来说,里面有两个线程,他们的进程id相同,都是11032,但是各自有独立的线程id,其中线程id为11302的为主线程(与进程id相同)。
* 但是对于同一个进程./thread2,上面打印出来的线程id与通过命令看到的线程id不相同,说明线程有两个线程id。
* 同一个线程的两个线程id,一个是pthread_t类型,一个是pid_t类型。通过pthread_create函数会产生一个线程id,这个线程id是pthread_t类型的,该线程id是属于线程库范畴的,在线程库里面对线程进行相关操作,都是通过该值进行;而pid_t类型的线程id,也就是LWP,是内核表示线程的唯一标识符,主要是操作系统为了进行线程调度。
* 通过gettid( )可以获得线程id,也就是可以获得PCB里面的pid的值,等于LWP的值;通过getpid( )可以获得进程id,也就是PCB里面tgid的值; 通过pthread_self( )获得的值就是pthread_t类型的线程id的值。

6.对于线程而言,没有父子之分,每个线程地位都相同,除了主线程会有些特殊之外。

猜你喜欢

转载自blog.csdn.net/xu1105775448/article/details/81061313