Linux多线程总结

简介:

一、线程的理解

1、线程其实是一个进程的一个执行流。

2、线程是操作系统调度的基本单位,进程是承担分配系统资源的基本单位。

3、linux下,一个进程就是一个独占资源的线程,即在这个地址空间仅有一个执行流,linux下的进程为轻量级进程(进程可以理解为是线程,可以理解为linux下均为线程),进程和线程均叫做pcb。

4、在一个进程中各线程还共享以下进程资源和环境:

1)文件描述符表

2)每种信号的处理方式式(SIG_IGN、 SIG_DFL或者定义的信号处理函数)

3)当前工作目录

4)用户id和组id

5、在一个进程中各线程各有一份:

1)线程id

2)上下文,包括各种寄存器的值、程序计数器和栈指针

3)栈空间

4)errno变量

5)信号屏蔽字
6.)调度优先级

  在Linux上线程函数位于libpthread共享库中,因此在编译时要加上-lpthread选项。

二、线程控制

1、创建线程

wKioL1nlox3Cq64TAABZzjru9To871.png

  

  返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。

  在一个线程中调用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_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self(3)可以获得当前线程的id。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_t tid;
void * thread_run( void  *val)
{
     printf ( "%s : pid is :%d,tid is : %u\n" ,
             ( char *)val,( int )getpid(),
             (unsigned  long  long )pthread_self());
     return  NULL;
}
int  main()
{
     int  err = pthread_create(&tid,NULL,thread_run,
             "other thread run" );
     if ( err != 0 ){
         printf ( "create thread error!info is :%s\n" ,
                 strerror (err));
     }
     printf ( "main thread run : pid is :%d,tid is : %u\n" ,
             ( int )getpid(),(unsigned  long  long )pthread_self());
     sleep(1);
     return  0;
}


运行结果:

wKioL1nlrAvhE9EeAABfoW_MVEo589.png

     可知在Linux上,thread_t类型是一个地址值,属于同一进程的多个线程调用getpid(2)可以得到相同的进程号,而调用pthread_self(3)得到的线程号各不相同。
  由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。
  如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行。

2、终止线程

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1) 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用。

2)一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3)线程可以调用pthread_exit终止自己。

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

3、线程等待

wKioL1nlwOiA5nT6AABIdAGFGQ4567.png

     返回值:成功返回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参数。

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
41
42
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void  *thread1( void  *val) 
{
     printf ( "thread 1 returning...\n" );
     return  ( void *)1;
}
void  *thread2( void  *val)
{
     printf ( "thread 2 exiting...\n" );
     pthread_exit(( void *)2);
}
void  *thread3( void  *val)
{
     while (1){
         printf ( "pthread 3 is running,wait for be cancal...\n" );
         sleep(1);
     }
     return  NULL;
}
 
int  main()
{
     pthread_t tid;
     void  *tret;
     pthread_create(&tid,NULL,thread1,NULL);
     pthread_join(tid,&tret);
     printf ( "thread1 return,thread1 id is:%u,return code is:%d\n" ,
             (unsigned  long )tid,( int )tret);
     pthread_create(&tid,NULL,thread2,NULL);
     pthread_join(tid,&tret);
     printf ( "thread2 exit,thread2 id is:%u,exit code is:%d\n" ,
             (unsigned  long )tid,( int )tret);
     pthread_create(&tid,NULL,thread3,NULL);
     sleep(4);
     pthread_cancel(tid);
     pthread_join(tid,&tret);
     printf ( "thread3 return,thread3 id is:%u,cancal code is:%d\n" ,
             (unsigned  long )tid,( int )tret);
     return  0;
}


wKioL1nlwf3xqs-lAADE6CA6Hcc794.png

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

  一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。 但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。 对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

4、线程分离

  在任何一个时间点上, 线程是可结合的(joinable)或者是分离的(detached) 。 一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。 相反, 一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

  默认情况下,线程被创建成可结合的。 为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。
  由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码:
pthread_detach(pthread_self())
或者父线程调用
pthread_detach(thread_id)(非阻塞,可立即返回)
这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有源。

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
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void  * thread ( void  *val)
{
     pthread_detach(pthread_self());
     printf ( "%s\n" ,( char  *)val);
     return  NULL;
}
int  main()
{
     pthread_t tid;
     int  tret = pthread_create(&tid,NULL, thread , "thread run..." );
     if ( tret != 0 ){
         printf ( "create error!,info:%s\n" , strerror (tret));
         return  tret;
     }
     int  ret = 0;
     sleep(1);
     if ( 0 == pthread_join(tid,NULL) ){
         printf ( "pthread wait success!\n" );
         ret = 0;
     }
     else {
         printf ( "pthread wait failed!\n" );
         ret = 1;
     }
     return  ret;
}

wKiom1nlz72C1W9TAABGYidXUHI519.png



本文转自 8yi少女的夢 51CTO博客,原文链接:http://blog.51cto.com/zhaoxiaohu/1973408,如需转载请自行联系原作者

相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
236 67
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
121 26
|
7月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
129 17
|
算法 Unix Linux
linux线程调度策略
linux线程调度策略
268 0
|
11月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
166 0
Linux C/C++之线程基础
|
11月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
存储 设计模式 NoSQL
Linux线程详解
Linux线程详解
|
缓存 Linux C语言
Linux线程是如何创建的
【8月更文挑战第5天】线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。
|
负载均衡 Linux 调度
在Linux中,进程和线程有何作用?
在Linux中,进程和线程有何作用?