《unix高级环境编程》线程——线程基本概述

线程的基本概念

        线程是一个进程中的控制和执行单元,也是 CPU 调度和分派的基本单元。在传统的 UNIX 系统中,一个进程只有一个线程(主线程),即在同一时刻只能做一件事情。在现代的 UNIX 系统中,一个进程可以包含多个线程,每个线程处理各自独立的任务,称之为“并发(concurrency)”,但并不等同于“并行(parallelism)”。真正意义上的并行只存在于多处理器系统中,而并发也可以存在于单处理器中。并行要求程序能够同时执行多个操作,而并发只要求程序能够假装同时执行多个操作,处理器的数量并不影响程序结构。

        进程是系统中程序执行和资源分配的基本单位,每个进程有自己的数据段、代码段和堆栈段。在进程中可以创建多个线程, 并发地完成多个不同的任务。线程各自有独自的线程ID、寄存器值、栈、调度优先级和策略、信号屏蔽字、errno 变量以及线程私有数据,而进程的可执行的程序文本、全局内存和堆内存、栈以及文件描述符对该进程的所有线程都是共享的。线程与进程之间的关系可由下图表示:

《unix高级环境编程》线程——线程基本概述_第1张图片

        多线程优点:

  1. 通过为每种事件类型的处理分配单独的线程,能够简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步编程模式,同步编程模式要比异步编程模式简单得多。
  2. 多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享,而多个线程自动地可以访问相同的存储地址空间和文件描述符。
  3. 有些问题可以通过将其分解从而改善整个程序的吞吐量。在单进程的情况下,多个任务只能串行完成;如果有多个线程,相互独立的任务的处理就可以交叉进行。
  4. 交互的程序同样可以通过使用多线程实现响应时间的改善,多线程可以把程序中处理用户输入输出的部分与其他部分分开。

        多线程缺点:

  1. 如果单个资源要在多个线程中共享,就必须处理一致性问题,也就是要进行相应的同步操作。如果使用过多的同步就很容易损失性能。
  2. 为了编写能够在多个线程中正确工作的代码就必须认真的思考和计划,不得不考虑死锁、竞争、优先级倒置等问题。
  3. 调试异步代码也比同步代码困难得多。调试不可避免地要改变事件的时序,如果某个线程因调试陷阱而运行的稍微慢了些,则你要跟踪的问题可能不会再现。跟踪内存错误将更加困难。

线程标识

        每个线程都有一个线程ID,线程ID只在它所属的进程环境中有效。线程ID使用 pthread_t 数据类型来表示,实现的时候可以使用一个结构来表示 pthread_t 数据类型,所以可移植的操作系统实现不能将它比作整数处理,因此必须使用 pthread_equal 函数来对比线程ID进行比较。 线程可以通过调用pthread_self 函数获得自身线程ID。

/* 线程 */

/*
 * 函数功能:比较两个线程ID;
 * 返回值:若相等则返回非0,否则返回0;
 * 函数原型:
 */
#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);

/*
 * 函数功能:获取自身的线程ID;
 * 返回值:调用线程的线程ID;
 * 函数原型:
 */
pthread_t pthread_self(void);

线程创建

        线程可以通过调用 pthread_create 函数创建。线程创建时并不能保证哪个线程会先运行,新创建的线程可以访问进程的地址空间,并且继承调用点成的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。在当前线程从函数pthread_create 中返回以及新线程被调度执行之间不存在同步关系,即新线程可能在当前线程从pthread_create 返回之前就运行了。甚至在 pthread_create 返回之前,新线程就可能已经运行完毕了。

/*
 * 函数功能:创建线程;
 * 返回值:若成功则返回0,否则返回错误编号;
 * 函数原型:
 */
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr,
                void *(*start_rtn)(void *), void *arg);
/*
 * 说明:
 * 当该函数成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID;
 * attr参数用于定制各种不同的线程属性,当为NULL表示创建默认属性的线程;
 * 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个指针参数arg,
 * 如果需要向start_rtn函数传递的参数不止一个,则需要把参数放在一个结构中,
 * 然后把这个结构的地址作为arg参数传入;
 */
测试程序:

#include "apue.h"
#include <pthread.h>

pthread_t ntid;

static void printids(const char *);
static void *func(void*);

int main(void)
{
    int err;

    err = pthread_create(&ntid, NULL, func, NULL);
    if(err != 0)
        err_quit("can't create thread: %s\n",strerror(err));
    printids("main thread: ");
    sleep(1);
    exit(0);
}

static void printids(const char *s)
{
    pid_t pid;
    pthread_t tid;

    pid = getpid();
    tid = pthread_self();

    printf("%s pid %u tid %u (0x%x)\n",s,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);
}

static void *func(void *arg)
{
    printids("new thread: ");
    return((void*)0);
}
输出结果:

main thread:  pid 4156 tid 3076343488 (0xb75d46c0)
new thread:  pid 4156 tid 3076340544 (0xb75d3b40)

        该程序实现的功能是在当前进程新建线程,并打印出主线程和新建线程的线程ID以及进程的ID,从结果可以知道,这两个线程在同一个进程中;注意:在 Linux下编译线程程序是必须加上 -lpthread 参数,因为 pthread 库不是Linux 系统默认的库,连接时需要使用库libpthread.a。编译过程:

gcc pthreadtest.c -o thread -lpthread


参考资料:

《UNIX高级环境编程》

你可能感兴趣的:(线程,线程创建,线程ID)