提问:
- 什么是线程?为什么需要线程?
- 如何使用线程?线程的使用接口?
- 线程的实际应用有哪些?可以与哪些技术结合?(这个以后我用了再来补充,或新开一节)
基本常识:
- 并发当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
- 并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
- 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔(一段时间)内发生。
- 在Linux线程实现中,线程ID在所有进程中都是唯一的。不过在其他实现中未必如此,SUSv3特别指出,应用程序若使用线程ID来标识其他进程的线程,其可移植性将无法得到保证。
一些英文缩写对应的全称:
- nptl 全称为 Native POSIX Threads Library 本人的渣渣翻译:本地POSIX线程库
- Pthreads 全称为 POSIX thread , 即POSIX线程。
- POSIX 全称为 Portable Operating System Interface of UNIX 本人的渣渣翻译:UNIX可移植系统接口
- copy-on-write 写时复制技术
- SUSv3 始于1999年,出于修订并加强POSIX标准和SUS规范的目的,IEEE、OPEN集团以及ISO/IEC联合技术委员会共同成立了奥斯丁公共标准修订工作组(CSRG)。于2001年12月,该工作组正式批准了POSIX 1003.1-2001,有时简称为POSIX.1-2001,有时也称为Single Unix Specification版本3,即SUSv3 。
1.什么是线程?为什么需要线程?
- 线程是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一程序的所有线程均会独立执行相同程序,且共享一份全局内存区域,其中包括初始数据段,未初始化数据段,堆内存段。
- 同一进程中多个线程可以并发执行,在多处理器环境下,多个线程可以同时并行。如果一个线程因等待I/O操作而遭到阻塞,其他线程仍可以运行。
- 进程间信息难以共享,由于除去只读代码段,父子进程并未共享内存,因此必须采用IPC方式,在进程间交换信息。此外,调用fork()来创建进程的代价相对较大,即便使用了写时复制技术,仍然需要复制内存页表和文件描述符等多种进程属性。
- 线程解决了上述两个问题,线程间能够方便快速地共享信息,即通过将数据复制到共享(全局或者堆)变量中,不过为了o防止多个线程同时修改同一份信息的情况出现,需要使用同步技术。线程无需复制内存页和页表,也就减少了时间和空间上的开销。
2.如何使用线程?线程的使用接口?
关于线程的基本背景信息:
- Pthreads函数返回状态与传统函数(返回0表示成功,返回-1表示失败)不同,所有Pthreads函数均以返回0表示成功,返回一个正值表示失败,且这一失败的返回值,与传统UNIX系统调用置于errno中的值含义相同。
- SUSv3并未规定如何实现Pthreads
头文件
#include<pthread.h>
!!!编译!!!需要带上线程库选项
gcc -o func func.c -lpthread
int pthread_create(pthread_t* thread, pthread_attr_t* attr,
void* (*start_routine)(void*),void* arg);
创建新的线程。
成功返回0,失败返回对应错误码。
参数thread为传出参数,保存新线程的标识。
参数attr是一个结构体指针,结构体元素分别指定新线程的运行属性,
attr可以由pthread_attr_init等函数设置各成员值。
通常传入NULL,表示使用默认属性。
参数start_routine是一个函数指针,指向新线程入口点函数。
线程入口点函数带有一个void*参数,由pthread_create第四个参数arg传入。
参数arg为参数start_routine的传入参数,可以为NULL,表示不传入。
也可以为结构体指针,一次传入多个参数。
pthread_t pthread_self(void);
线程使用该函数可以获取自己的id。
返回对应的线程id,供其他Pthreads函数使用。
int pthread_equal(pthread_t t1, pthread_t t2);
比较两个线程id是否相同。
如果t1和t2相同,返回非0值。反之,返回0 。
注:由于pthread_t作为一种不透明的数据类型(没有定义相应标准),
它可能是结构,也可能是指针,也可能是标量。
所以这个函数是相当有必要的。(不能用==去比较)
连接已终止的线程
int pthread_join(pthread_t thread, void **thread_return);
该函数是一个阻塞函数,等待参数thread标识的线程终止,称为连接(joining)。
如果线程已经终止,该函数会立即返回
thread_return是一非空指针,将会保存线程终止时返回值的拷贝。
如果线程有pthread_exit()函数终止,函数中参数可以被pthread_join函数当作返回值接收到。
pthread不回收堆内存,只回收线程栈内存和内核中struct task_struct结构占用的内存
例子
#include <func.h>
void* threadFunc(void* p)
{
printf("I am child thread, p = %ld\n", (long)p);//使用传入参数
pthread_exit((void**)2);
}
int main(int argc, char* argv[])
{
pthread_t pthid;
int ret;
ret = pthread_create(&pthid, NULL, threadFunc, (void*)1);//传入参数
THREAD_ERROR_CHECK(ret, "pthread_create");
printf("I am main thread, mythread id = %ld\n", pthread_self());
long result;
pthread_join(pthid, (void**)&result);//接收参数
printf("i am parent , return number = %ld\n", result);
return 0;
}
-
如向pthread_join传入一个之前连接过的线程ID,将会导致无法预知的行为。有可能再度连接一个新建立的线程。
-
若线程未分离,则必须使用pthread_join()来进行连接。如果未进行连接,那么线程终止时会产生僵尸线程,与僵尸进程概念类似,一是会浪费系统资源,二是如果僵尸线程积累过多,可能导致应用无法创建新的线程。
线程的取消
在Linux中一个线程可以被另一个线程取消(cancer),具体的方法是:一个线程向目标线程发出cancer信号,但是如何处理cancer信号由目标线程自己决定,目标线程或者忽略,或者终止,或者运行至canceration-point(取消点)后终止。
int pthread_cancer(pthread_t thread);
向线程号thread表示的线程发送一个请求,要求其立即退出。
成功返回0,失败返回一个正的错误号。
线程的终止
- 线程start函数执行return语句并返回指定值
- 线程调用pthread_exit()
- 调用pthread_cancel()取消进程
- 任意线程调用exit(),或者主线程执行了return语句(main函数),都会导致进程中所有线程立即终止。
void pthread_exit(void* retval);
退出当前线程。
其参数retval可以被其他线程用pthread_join函数捕获。
但是注意retval所指向的内容不应该分配于线程栈中,不然随着线程结束,
线程栈销毁,会导致地址空间被回收,不再是原来的数据。
- 调用pthread_exit()相当于在线程的start函数中执行return,不同之处在于可在线程start函数所调用的任意函数中调用pthread_exit()。
- 如果主线程调用了pthread_exit(),而非调用exit()或者执行return语句,那么其他线程将继续运行。
线程的分离
- 默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过调用pthread_join()获取其返回状态。有时,程序员不关心线程的返回状态,只是希望系统在线程终止时能自动清理并移除之。在这种情况下,可以调用pthread_detach()并向thread参数传入指定线程的标识符,将该线程标记为处于分离(detached)状态。
- 一旦分离,无法通过调用pthread_join()获取其返回状态,也无法使其返回“可连接”状态。
- 其他线程调用exit(),主线程执行return语句,还是会影响到分离的线程。(共用一个内存空间)
int pthread_detach(pthread_t thread);
使线程号thread对应线程置于分离状态
成功返回0,失败返回对应错误号
线程的属性
一些需要注意的线程属性:
- 线程栈的位置和大小。
- 线程调度策略和优先级。
- 线程是否处于可连接或分离状态。
总结
- 线程是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一程序的所有线程均会独立执行相同程序,且共享一份全局内存区域,其中包括初始数据段,未初始化数据段,堆内存段(也就是共享相同的全局变量和堆变量)。但每个线程都配有用来存放局部变量的私有栈。同一进程的线程还共享一些其他属性,包括进程ID,打开的文件描述符,信号处置,当前工作目录以及资源限制。
- 线程与进程关键区别在于,线程比进程更容易共享信息,创建线程比进程要快。