Linux系统应用编程---线程原语

简介: Linux系统应用编程---线程原语

线程概念

什么是线程

LWP: light wight process,轻量级进程,本质还是进程(在Linux环境下)

进程:独立地址空间,拥有PCB

线程:也有PCB,但没有独立的地址空间(共享)

区别:在于是否共享地址空间

Linux下:     进程:最小的执行单位

                    线程:最小分配资源单元,可看成是只有一个进程的线程

Linux线程实现原理

1、轻量级线程也有PCB,创建线程使用的底层函数和进程一样,都是clone

2、从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表示相同的,

3、进程可以蜕变成线程

4、线程可看做寄存器和栈的结合

5、在Linux下,线程是最小的执行单位,进程是最小的资源分配单位。

查看LWP号

ps -lf pid              查看指定进程的lwp号

ps –aux          看进程

ps –eLf          看线程

ps –Lw pid     查看某一个进程的线程有那些

线程共享资源

1、文件描述符表

2、每种信号的处理方式

3、当前工作目录

4、用户ID和组ID

5、内存地址空间(Text/data/bss/堆/共享库)

 

线程非共享资源

1.线程id

2.处理器现场和栈指针(内核栈)

3.独立的栈空间(用户空间栈)

4.errno变量

5.信号屏蔽字

6.调度优先级

 

线程优缺点

优点:

提高程序的并发性

开销小,不用重新分配内存

通信和共享数据方便

缺点:

线程不稳定(库函数实现)

线程调试比较困难(gdb支持不好)

线程无法使用unix经典事件,例如信号

 

线程原语

1、pthread_create

1. #include <pthread.h>
2. 
3. int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
4. 
5. 
6. pthread_t *thread:传递一个pthread_t变量地址进来,用于保存新线程的tid(线程ID)
7. 
8. const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL
9. 
10. void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块
11. 
12. void *arg:指定线程将要加载调用的那个函数的参数

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

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

 

2、pthread_self

1. #include <pthread.h>
2. 
3. pthread_t pthread_self(void);

获取调用线程tid,其作用对应进程中getpid()函数。

线程ID:pthread_t类型,其本质是在Linux下午无符号整数,其他系统中可能是结构体实现

线程ID是进程内部识别标志。(两个进程间,线程ID允许相同)

注意:不应该使用全局变量pthread_t tid,在子进程中通过pthread_create传出参数来获取线程ID,而应该使用pthread_self()

 

线程ID与线程号的区别:

Ps –eLf命令看到的LWP那一列是线程号,线程号是CPU分配时间轮片的依据

Pthread_self得到的是线程ID,线程ID用来在进程内部区分线程

 

程序1:循坏创建多个子线程

1. #include <stdio.h>
2. #include <string.h>
3. #include <unistd.h>
4. #include <stdlib.h>
5. #include <pthread.h>
6. 
7. void *thread_func(void *arg)
8. {
9.  int i = (int )arg;
10.   sleep(i);
11.   printf("%dth thread: thread id = %lu, pid = %u\n", i+1, pthread_self(), getpid());
12. 
13.   return NULL;
14. }
15. 
16. int  main(void)
17. {
18.   pthread_t tid;
19.   int ret, i;
20. 
21.   for(i=0; i<5; i++){
22.     ret = pthread_create(&tid, NULL, thread_func, (void *)i);
23.     if(ret != 0){
24.       fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
25.       exit(1);
26.     }
27.   }
28. 
29.   //sleep(i);
30. 
31.   //return 0;     //将当前进程退出
32.   pthread_exit(NULL);
33. 
34. }

执行结果如下:

线程默认共享数据段、代码段等地址空间,常用的是全局变量,而进程不共享全局变量,只能借助mmap.

 

程序2:验证线程之间共享全局数据

1. #include <stdio.h>
2. #include <string.h>
3. #include <unistd.h>
4. #include <stdlib.h>
5. #include <pthread.h>
6. 
7. int var = 100;
8. 
9. void *tfn(void *arg)
10. {
11.   var = 200;
12.   printf("thread.\n");
13. 
14.   return NULL;
15. }
16. 
17. int  main(void)
18. {
19.   printf("At first var = %d.\n", var);
20. 
21.   pthread_t tid;
22.   pthread_create(&tid, NULL, tfn, NULL);
23.   sleep(1);
24. 
25.   printf("after pthread_create, var = %d.\n", var);
26. 
27.   return 0;     //将当前进程退出
28. 
29. }

执行结果如下:

3、pthread_exit

1. #include <pthread.h>
2. 
3. void pthread_exit(void *retval);

void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。一般传NULL。

pthread_exit与exit的区别:

调用线程退出函数pthread_exit,只是推出当前线程

任何线程里使用exit都会导致进程退出,主控线程退出时不能return或exit,因为会影响其他线程未工作。

 

exit与_exit的区别:

exit关闭C标准文件流,并刷新文件缓冲区

_exit(Linux底层函数),导致进程退出,关闭未关闭的文件描述符

 

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

 

4、pthread_join

阻塞等待线程退出,获取线程退出状态,其作用对应进程中的waitpid()函数

1. #include <pthread.h>
2. 
3. int pthread_join(pthread_t thread, void **retval);

pthread_t thread:回收线程的tid

void **retval:接收退出线程传递出的返回值

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

5、pthread_cancel

在进程内的某个线程可以取消另一个线程

1. #include <pthread.h>
2. 
3. int pthread_cancel(pthread_t thread);

6、pthread_detach

1. #include <pthread.h>
2. 
3. int pthread_detach(pthread_t tid);

pthread_t tid:分离线程tid

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

 

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

7、pthread_equal

比较两个线程是否相等

 

线程终止方式

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1.从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。

2.一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

3.线程可以调用pthread_exit终止自己

目录
相关文章
|
5天前
|
Java
并发编程之线程池的应用以及一些小细节的详细解析
并发编程之线程池的应用以及一些小细节的详细解析
17 0
|
16天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
4天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
24 0
|
16天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
1天前
|
缓存 Java
Java并发编程:深入理解线程池
【4月更文挑战第26天】在Java中,线程池是一种重要的并发工具,它可以有效地管理和控制线程的执行。本文将深入探讨线程池的工作原理,以及如何使用Java的Executor框架来创建和管理线程池。我们将看到线程池如何提高性能,减少资源消耗,并提供更好的线程管理。
|
4天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
4天前
|
固态存储 Ubuntu Linux
Linux(29) 多线程快速解压缩|删除|监视大型文件
Linux(29) 多线程快速解压缩|删除|监视大型文件
11 1
|
9天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
10天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
11天前
|
缓存 监控 Java
Java并发编程:线程池与任务调度
【4月更文挑战第16天】Java并发编程中,线程池和任务调度是核心概念,能提升系统性能和响应速度。线程池通过重用线程减少创建销毁开销,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。任务调度允许立即或延迟执行任务,具有灵活性。最佳实践包括合理配置线程池大小、避免过度使用线程、及时关闭线程池和处理异常。掌握这些能有效管理并发任务,避免性能瓶颈。