多线程

本文按照Unix环境高级编程总结而成:
  • 线程概念
典型的进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情。有了多个线程以后,就可以把进程设计成在某一时刻能够做多件事情,每个线程各自处理独立的任务。这种设计的好处有:
  1. 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。
  2. 进程间实现内存和文件描述符的共享需要依靠操作系统提供的复杂机制,但是进程内的多个线程可以自动共享一些信息,如:代码、全局空间、堆和文件描述符。
  3. 有些问题分解可以提高整个程序的性能。单线程处理多个任务的情况下,需要把这些任务串行化。但是有多个线程的情况下,相互独立的任务可以在多个线程上交叉执行。
  4. 多线程并非只在多核处理器才能发挥作用,在单核处理器上也能发挥多线程编程模型的好处。多线程程序在执行串行化任务时不得不阻塞,由于某些线程阻塞时其他线程还可以运行,所以多线程程序在单核处理器上运行依旧可以改善性能。
  • 线程标识
就像每个进程有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中是唯一的,而线程ID只在所属进程唯一。
线程ID用pthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread_t数据类型,所以可移植的操作系统实现不能把它作为整数处理,必须使用函数来对两个线程ID进行比较。
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);  若相等,返回非0数值;否则返回0.
线程可以通过调用pthread_self函数获得自身的线程ID。
pthread_t pthread_self(void);
  • 线程创建
在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的,新增的线程可以通过调用pthread_create函数创建。
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t* restrict attr, void *(*start_rtn)(void*), void *restrict arg);
创建成功,返回0;否则,返回错误编号
当pthread_create成功返回时,tidp指向的内存单元被设置成线程ID,attr参数用于定制各种不同的线程属性。把它设置NULL时创建一个具有默认属性的线程。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递多个函数,需要把这些参数放到一个结构中,然后将这个结构的地址作为arg参数传入。
线程创建时并不能保证调用线程还是新创建的线程会先运行。
#include "apue.h"
#include <pthread.h>

pthread_t ntid;

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

    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
      (unsigned long)tid, (unsigned long)tid);
}

void *thr_fn(void *arg)
{
    printids("new thread: ");
    return((void *)0);
}

int main(void)
{
    int     err;

    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
        err_exit(err, "can't create thread");
    printids("main thread:");
    sleep(1); //主线程休眠,如果不休眠,可能会退出,这样新线程还没运行,整个进程就终止了。
    exit(0);
}
结果:

 两个线程具有相同的进程ID,从十六进制来看,linux使用指向线程数据结构的指针作为它的线程ID。

  • 线程终止
线程有三种方式退出:
  1. 从线程的开始函数中返回,返回值是线程的退出码。
  2. 自己调用pthread_exit。
  3. 被同一进程中的其他线程取消。
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);    成功返回0;否则返回错误编号
执行pthread_join时,调用线程将一直阻塞,直到指定线程退出。如果指定线程主动退出,则rval_ptr就包含返回码;如果指定线程被取消,rual_ptr指定的内存单元就设置为PTHREAD_CANCELED。如果对线程的返回值不感兴趣,可以把rval_ptr设置为NULL。
#include "apue.h"
#include <pthread.h>

void *thr_fn1(void *arg)
{
    printf("thread 1 returning\n");
    return((void *)1);
}

void *thr_fn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}

int main(void)
{
    int         err;
    pthread_t   tid1, tid2;
    void        *tret;

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0)
        err_exit(err, "can't create thread 1");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0)
        err_exit(err, "can't create thread 2");
    err = pthread_join(tid1, &tret);
    if (err != 0)
        err_exit(err, "can't join with thread 1");
    printf("thread 1 exit code %ld\n", (long)tret);
    err = pthread_join(tid2, &tret);
    if (err != 0)
        err_exit(err, "can't join with thread 2");
    printf("thread 2 exit code %ld\n", (long)tret);
    exit(0);
}
结果:

可以看到当 一个线程通过调用pthread_exit退出或者从入口函数返回时,进程中的其他线程可以通过调用pthread_join获得该线程的退出状态。
pthread_exit函数的无类型指针可以传递包含复杂信息的结构的地址,但是,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的。
例如,在调用线程的栈上分配了该结构,那么其他线程在使用这个结构时内存内容可能就发生了变化;又如,线程在自己的栈上分配了结构,然后把这个结构的指针传给了pthread_exit,那么调用pthread_join的线程使用该结构时,这个栈可能就被撤销或者另作他用。
#include "apue.h"
#include <pthread.h>

struct foo {
    int a, b, c, d;
};

void printfoo(const char *s, const struct foo *fp)
{
    printf("%s", s);
    printf("  structure at 0x%lx\n", (unsigned long)fp);
    printf("  foo.a = %d\n", fp->a);
    printf("  foo.b = %d\n", fp->b);
    printf("  foo.c = %d\n", fp->c);
    printf("  foo.d = %d\n", fp->d);
}

void *thr_fn1(void *arg)
{
    struct foo  foo = {1, 2, 3, 4};

    printfoo("thread 1:\n", &foo);
    pthread_exit((void *)&foo);
}

void *thr_fn2(void *arg)
{
    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());
    pthread_exit((void *)0);
}

int main(void)
{
    int         err;
    pthread_t   tid1, tid2;
    struct foo  *fp;

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0)
        err_exit(err, "can't create thread 1");
    err = pthread_join(tid1, (void *)&fp);
    if (err != 0)
        err_exit(err, "can't join with thread 1");
    sleep(1);
    printf("parent starting second thread\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0)
        err_exit(err, "can't create thread 2");
    sleep(1);
    printfoo("parent:\n", fp);
    exit(0);
}
结果:

 可以看到当主线程方位这个结构时,结构的内容已经改变。具体如何改变据说根据内存体系结构、编译器以及线程库的实现会有所不同。
线程可以通过调用pthread_cancel来请求取消同一进程中的其他线程。
int pthread_cancel(pthread_t tid);
在默认情况下,pthread_cancel会使得由tid标识的线程的行为表现如同调用了参数为PTHREAD_CANCELED的pthread_exit函数。但是线程可以选择忽略取消或者控制如何被取消。
pthread_cancel并不等待线程的终止,它仅仅提出要求。
线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。一个线程可以建立多个清理处理程序。处理程序记录在占中,它们的执行顺序与注册时相反。(待补充)
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
void pthread_cleanup_pop(int execute);
默认情况下,线程的终止状态会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被回收。在线程分离后,不能用pthread_join等待它的终止状态,对分离的线程调用pthread_join会产生未定义行为。可以调用pthread_detach分离线程。
int pthread_detach(pthread_t tid);

 

posted @ 2023-04-18 10:34  时间在哪  阅读(31)  评论(0)    收藏  举报