做人呢,最紧要就系开心啦

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

543次阅读
没有评论

多线程解决什么问题:

  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协议发布,转载请注明出处。
评论(没有评论)