Linux线程

本文最后更新于:2021年4月9日 晚上

概览:Linux线程

线程

进程与线程类似,是允许应用程序并发执行多个任务的一种机制。

  • 一个进程可以包含多个线程。

  • 同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,包括初始化数据段、未初始化数据段、以及堆内存段。

    • 传统意义上的UNIX进程只是多线程程序的一个特例,该进程只包含一个线程。
  • 进程是CPU分配资源的最小单位,线程则是操作系统调度执行的最小单位。

  • Linux中线程的本质仍然是进程,线程是轻量级的进程。(LWP:Light Weight Process)

查看执行进程的线程号:

1
ps -Lf pid

查看当前pthread库的版本

1
getconf GUN_LIBPHREAD_VERSION

线程和进程的区别

  • 信息共享
    • 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
    • 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
  • 创建代价
    • 调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。
    • 创建线程比创建进程通常要快10倍甚至更多,线程间是共享虚拟地址空间,无需采用写时复制技术来复制内存,也无需复制页表。

线程共享与非共享的资源

共享资源

  • 进程ID和父进程ID
  • 进程组ID和会话ID
  • 用户ID和用户组ID
  • 文件描述符表
  • 信号处置
  • 文件系统的相关信息:文件权限掩码(umask),当前工作目录
  • 虚拟地址空间除了栈、.text代码段)代码段会发生变化分成一个个的段、栈也是。
  • 共享库、堆这些都是共享的。

非共享资源

  • 线程ID
  • 信号掩码 – 阻塞信号集
  • 线程特有数据
  • error变量
  • 实时调度策略和优先级
  • 栈、本地变量和函数的调用链接信息。

注意事项

  • pthread不是linux的标准库,需要额外引入一个静态库-lpthread或者-pthread编译链接之后才能执行程序。
  • 一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。
  • 程序默认只有一个线程,pthread_create()函数调用之后,就像fok()一样,产生两个线程。
  • 线程回收不一定只能主线程回收其他子线程,任何线程都可以回收其他线程。
  • 线程相关函数创建的错误号不同于之前进程中的那些错误号,需要区别对待!
  • 可以通过pthread_self()函数来获取线程id。
  • int pthread_equal(pthread_t t1, pthread_t t2);可以来对比两个线程id是否相同,由于不同的操作系统,其pthread_t类型实现不太一样,有的是无符号的长整型,有的是使用结构体去实现的,所以使用封装好的函数对比,容易提高代码复用性。

pthread_create 创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

/*
- 功能:创建一个子线程
- 参数:
- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
- attr : 设置线程的属性,一般使用默认值,NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
- arg : 给第三个参数使用,传参
- 返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: char * strerror(int errnum);
*/

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void * callback(void *arg){
printf("i am child pthread\n");
printf("child thread recv a arg : %d\n",*(int *)arg);
return NULL;
}

int main(){

printf("main pthread: %ld \n",pthread_self());

pthread_t tid;

int num = 10;
int ret = pthread_create(&tid,NULL,callback,(void *)&num);
if(ret != 0){
char* errstr = strerror(ret);
printf("error : %s\n",errstr);
}

//主线程等待子线程结束,否则主线程直接return 0,
//整个进程的资源都被回收,子线程也就戛然而止了。
for(int i=0;i<5;i++){
printf("num:%d, main pthread : %ld, child pthread : %ld\n",
i,pthread_self(),tid);
sleep(1);
}

return 0;
}

pthread_exit 线程终止

1
2
3
4
5
6
7
#include <pthread.h>
void pthread_exit(void *retval);
/*
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
参数:
retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。
*/
  • 即使主线程终止了,也不会影响其他线程的执行。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg){
printf("child thread pally\n");
return NULL;
}

int main(){

pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
printf("error: %s\n",strerror(ret));
}

for(int i=0;i<5;i++){
printf("main pthread : %d\n",i);
}

//主线程退出,主线程退出后,也不会影响其他线程的执行
pthread_exit(NULL);

printf("main thread exit\n");//不会被执行

return 0;//显然也不会执行
}

pthread_join 连接 【阻塞函数】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
/*
- 功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
任何线程都可以回收任何线程。
一般在主线程中使用
- 参数:
- thread:需要回收的子线程的ID
- retval: 接收子线程退出时的返回值,这里是二层指针
- 返回值:
0 : 成功
非0 : 失败,返回的错误号
*/
  • retval是一个二层指针,来保存相关联线程exit退出时的参数。

    • 由于exit退出时的参数保存在了void *retval之中。

    • 要获取上述的那个参数,也需要一个指针来保存,但是我们传递的指针需要接收完结果之后还要能够使用,所以不能使用一个单纯的指针,那样就像是普通的参数传递。

    • 在C语言中要使得传入的参数在函数中改变还能拿得到,使用指针是最好的选择。

    • 例子:

    • int a = 10;
      
      void func(int b){};//想通过func来改变a的值
      
      void func(int *b){} //这样就可以
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40

      实例:

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

      int gloabl = 20;

      void *callback(void * arg){
      printf("child pthread : %ld\n",pthread_self());
      gloabl = 30;//得是全局变量,局部变量在函数结束之后,栈回收,变量随之销毁!
      pthread_exit((void *)&gloabl);
      }

      int main(){

      pthread_t tid;
      int ret = pthread_create(&tid,NULL,callback,NULL);
      if(ret != 0){
      printf("error1 : %s\n",strerror(ret));
      }

      printf("child : %ld , parent: %ld \n",tid,pthread_self());

      //回收资源
      int * thread_retval;//接收子线程退出的数据
      ret = pthread_join(tid,(void **)&thread_retval);
      if(ret != 0){
      printf("error2 : %s\n",strerror(ret));
      }
      printf("join data : %d\n",*thread_retval);//30

      pthread_exit(NULL);

      return 0;
      }
      ## pthread_deach 分离线程
1
2
3
4
5
6
7
8
9
10
11
#include <pthread.h>
int pthread_detach(pthread_t thread);
/*
- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
1.不能多次分离,会产生不可预料的行为。
2.不能去连接一个已经分离的线程,会报错。
- 参数:需要分离的线程的ID
- 返回值:
成功:0
失败:返回错误号
*/

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void * callback(void *arg){

}

int main(){

pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
printf("error:%s\n",strerror(ret));
}

printf("main pthread:%ld, child pthread: %ld\n",pthread_self(),tid);

//设置子线程分离,子线程结束时对应的资源不需要主线程释放
ret = pthread_detach(tid);
if(ret != 0){
printf("error2: %s\n",strerror(ret));
}

//尝试连接子线程
//ret = pthread_join(tid,NULL);
//if(ret!= 0){
// printf("error 3:%s\n",strerror(ret));//error 3:Invalid argument
// }

pthread_exit(NULL);

return 0;
}

pthread_cancle 取消线程

1
2
3
4
5
6
7
8
9
#include <pthread.h>
int pthread_cancel(pthread_t thread);
/*
- 功能:取消线程(让线程终止)
取消某个线程,可以终止某个线程的运行,
但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。
取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换
这个位置称之为取消点。
*/

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
printf("chid thread id : %ld\n", pthread_self());
for(int i = 0; i < 5; i++) {
printf("child : %d\n", i);
}
return NULL;
}

int main() {

// 创建一个子线程
pthread_t tid;

int ret = pthread_create(&tid, NULL, callback, NULL);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error1 : %s\n", errstr);
}

// 取消线程,即终止线程
pthread_cancel(tid);

for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}

// 输出主线程和子线程的id
printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());


pthread_exit(NULL);

return 0;
}

线程设置属性

1
2
3
4
5
6
7
8
9
10
11
int pthread_attr_init(pthread_attr_t *attr);
// - 初始化线程属性变量

int pthread_attr_destroy(pthread_attr_t *attr);
// - 释放线程属性的资源

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
// - 获取线程分离的状态属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
//- 设置线程分离的状态属性

应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void * callback(void * arg){
printf("child pthread\n");
}

int main(){

pthread_attr_t attr;
pthread_attr_init(&attr);

//设置属性
//属性 PTHREAD_CREATE_JOINABLE,就是一个默认属性!
//It is an error to specify the thread ID of a thread that was created
// in a detached state in a later call to
//pthread_detach(3) or pthread_join(3).
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

pthread_t tid;
int ret = pthread_create(&tid,&attr,callback,NULL);

//尝试获取线程的栈大小
size_t stacksize;
pthread_attr_getstacksize(&attr,&stacksize);

printf("stack size is %ld\n",stacksize);

//销毁属性
pthread_attr_destroy(&attr);

//关闭主线程
pthread_exit(NULL);
return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!