Linux之线程——nptl线程库(一)

提问:

  1. 什么是线程?为什么需要线程?
  2. 如何使用线程?线程的使用接口?
  3. 线程的实际应用有哪些?可以与哪些技术结合?(这个以后我用了再来补充,或新开一节)

基本常识:

  • 并发当有多个线程在操作时,如果系统只有一个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

!!!编译!!!需要带上线程库选项
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 

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,打开的文件描述符,信号处置,当前工作目录以及资源限制。
  • 线程与进程关键区别在于,线程比进程更容易共享信息,创建线程比进程要快。

 

你可能感兴趣的:(linux下c语言开发细节)