Linux多线程1:多线程生命周期

520次阅读
没有评论

多线程解决什么问题:

  1. 线程生命周期问题;
  2. 线程如何划分的问题(性能问题);
  3. 线程如何通信的问题;
    信号的行为是进程级别的;对任何线程发信号,线程组都会响应;

线程与进程关系

多线程通信开销(不涉及大量数据交互),远低于多进程;
多进程比多线程好调试;

关系不亲密,适合用多进程模型;否则用多线程模型

线程的概念

由于同一进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享

以下进程资源和环境
文件描述符表
每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id

但有些资源是每个线程各有一份的
线程id
上下文,包括各种寄存器的值、程序计数器和栈指针
栈空间
errno变量
信号屏蔽字
调度优先级

Linux使用的线程库是由POSIX标准定义的,称为POSIX thread或者pthread。线程函数位于libpthread共享库中,因此在编译时要加上-lpthread选项。
POSIX标准对线程的要求:
Linux多线程1:多线程生命周期

由上可见Linux内核通过clone创建线程并不能支持POSIX标准,Linux通过NPTL模型支持POSIX;

leon@pc:~$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.23
leon@pc:~$

在NPTL模型,同一个进程内的线程提供不同PID, 共用一个TGID;

Top命令看到的PID实际是TGID(等于主线程的PID);
Top –H线程视角看到的是每个线程的PID(不同,内核存在的真正PID);

线程ID

我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。

线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self(3)可以获得当前线程的id。
在Linux上,thread_t类型是一个地址值,属于同一进程的多个线程调用getpid(2)可以得到相同的进程号,而调用pthread_self(3)得到的线程号各不相同。

pthread_self是在C库实现的数据结构(TCB),与内核没关系;

多线程生命周期

1 线程的创建

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

返回值:成功返回0,失败返回错误号。

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。

start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。

start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。
start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态,稍后详细介绍pthread_join。

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

pthread_t ntid;

void printids(const char *s)
{ 
        pid_t pid; 
        pthread_t tid; 
        pid = getpid(); 
        tid = pthread_self(); 
        printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
}
void *thr_fn(void *arg)
{ 
        printids(arg); 
        return NULL;
}
int main(void)
{ 
        int err; 
        err = pthread_create(&ntid, NULL, thr_fn, "new thread: "); 
        if (err != 0) 
        { 
                fprintf(stderr, "can't create thread: %s\n", strerror(err)); 
                exit(1); 
        } 
        printids("main thread:"); 
        sleep(1); 
        return 0;
}

2 线程终止方法:

(1)从线程函数return; 这个方法对主线程不适用,从main函数return,会调用exit();
(2)一个线程可以调用pthread_cancel终止同一进程中的另一个线程。(会导致资源同步问题,在android中已经弃用);
(3)线程可以调用pthread_exit终止自己;

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

value_ptr是void *类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join获得这个指针。

整个进程退出
(4)main函数返回;
(5)调用进程级别的API;exit(),_exit();
(6)某一线程做了非常操作,引起segment fault;

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

如果任意一个线程调用了exit或_exit,会调用exit_group(), 则整个进程的所有线程都终止;

从main函数return会调用exit,相当于调用exit_group;

退出单个线程,用pthread_exit();

exit()退出,调用exit_group(),所有线程退出;
pthread_exit()退出,单个线程退出;

资源的单位是进程;

线程退出,可能引起memory leak等leak;

线程不pthread_join可能引起leak;

3 join线程

#include <pthread.h>
int pthread_join(pthread_t thread, void **value_ptr);

返回值:成功返回0,失败返回错误号。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

(1)如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。

(2)如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。

(3)如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *thr_fn1(void *arg)
{ 
        printf("thread 1 returning\n"); 
        return (void *)1;
}
void *thr_fn2(void *arg)
{ 
        printf("thread 2 exiting\n"); 
        pthread_exit((void *)2);
}
void *thr_fn3(void *arg)
{ 
        while(1) 
        { 
                printf("thread 3 writing\n"); 
                sleep(1); 
        }
}
int main(void)
{ 
        pthread_t tid; 
        void *tret;
        pthread_create(&tid, NULL, thr_fn1, NULL); 
        pthread_join(tid, &tret); 
        printf("thread 1 exit code %d\n", (int)tret); 
        pthread_create(&tid, NULL, thr_fn2, NULL);
        pthread_join(tid, &tret); 
        printf("thread 2 exit code %d\n", (int)tret); 
        pthread_create(&tid, NULL, thr_fn3, NULL); 
        sleep(3); 
        pthread_cancel(tid); 
        pthread_join(tid, &tret);
        printf("thread 3 exit code %d\n", (int)tret); 

        return 0;
}
leon@pc:~$ ./a.out 
thread 1 returning
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2
thread 3 writing
thread 3 writing
thread 3 writing
thread 3 exit code -1

可见在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:

#define PTHREAD_CANCELED ((void *) -1)

pthread_join()退出,释放线程资源;如果线程不做pthread_join,线程栈不会释放,线程在堆上malloc的内存也不会释放(进程没有退出);

4 线程detach

如果线程被设置为detach状态,线程一旦终止就立刻回收它占用所有资源,
而不保留终止状态;

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

detach工程上很少用,只有调试线程用;

5多线程的信号处理

信号是进程级的概念,给某个进程发信号,该进程内所有线程都会响应;
对同一个信号,多个线程都设置处理函数,则最后一个设置生效;

POSIX要求可以对特定的线程发送信号,用pthread_kill()实现;

线程组有共用的sispending, 每个线程有私有的sispending;

pthread_kill()对应私有sispending;

正文完
 1
admin
版权声明:本站原创文章,由 admin 于2022-01-13发表,共计4570字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
粤ICP备2021172357号-1