Linux/UNIX线程(1)

线程(1)

本文将介绍怎样使用多个控制线程在单个进程环境中运行多个任务。

一个进程中的全部线程都能够訪问该进程的组成部件(如文件描写叙述符和内存)。

线程包含了表示进程内运行环境必须的信息,当中包含进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。进程的全部信息对该进程的全部线程都是共享的,包含可运行的程序文本、程序的全局内存和堆内存、栈以及文件描写叙述符。

线程标识

进程ID在整个系统中是唯一的。每一个线程ID。线程ID仅仅在它所属的进程环境中有效。

在线程中。线程ID的类型是pthread_t类型,因为在Linux下线程採用POSIX标准,所以,在不同的系统下。pthread_t的类型是不同的。比方在ubuntn下,是unsigned long类型。而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读。而应该使用pthread_equal来推断。

#include<pthread.h>

intpthread_equal(pthread_t t1, pthread_t t2);

返回值:若相等则返回非0值,否则返回0。

线程能够通过调用

#include<pthread.h>

pthread_tpthread_self(void);

返回值是调用线程的线程。

线程创建

创建线程能够调用pthread_create函数创建。

#include<pthread.h>

intpthread_create(pthread_t *thread, const pthread_attr_t *attr,

void*(*start_routine) (void *), void *arg);

返回值:若成功则返回0,否则返回错误编号

当pthread_create成功返回时,由thread指向的内存单元被设置为新创建线程的线程ID。

attr參数用于定制各种不同的线程属性。

将attr设置为NULL,创建默认属性的线程。

新创建的线程从start_routine函数地址開始执行,该函数仅仅有一个无类型指针參数arg。假设须要向start_routine函数传递的參数不止一个,那么须要把这些參数放到一个结构中,然后把这个结构的地址作为arg參数传入。

线程创建时并不能保证哪个线程会先执行:是新创建的线程还是调用线程。新创建的线程能够訪问进程的地址空间,而且继承调用线程的浮点环境和信号屏蔽字。但该线程的未决信号集被清除。

以下的程序创建了一个线程而且打印进程ID、新线程的线程ID以及初始化线程的线程ID。

#include <stdio.h>
#include <stdlib.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 %u tid %u (0x%x)\n", s, (unsigned int)pid,
      (unsigned int)tid, (unsigned int)tid);
}

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

void err_quit(const char* fmt, ...)
{
printf("%s\n",fmt);
exit(1);
}

int main(void)
{
    int     err;

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

编译:

 pthread 库不是 Linux 系统默认的库,连接时须要使用静态库 libpthread.a。所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,须要链接该库。

gcc creatThread.c –lpthread

运行及输出:

./a.out

main thread: pid2846 tid 3079362240 (0xb78b56c0)

new thread:  pid 2846 tid 3079359344 (0xb78b4b70)

主线程须要休眠,否则整个进程有可能在新线程进入执行之前就终止了。新线程是通过pthread_self()来获取自己的线程ID,而不是从共享内存中读出或者从线程的启动例程中以參数接收到。pthread_create函数会通过第一个參数返回新建线程的ID。在本例中。新建线程ID被放在ntid中,可是假设新线程在主线程调用pthread_create返回之前就执行了,那么新线程看到的是未初始化的ntid内容。

线程终止

假设进程中的任一线程调用了exit,_Exit或者_exit。那么整个进程就会终止。一次类似,假设信号的默认动作是终止进程。那么,把该信号发送到线程会终止整个进程。

但线程能够通过下列三种方式退出:

1.      线程仅仅是从启动例程中返回。返回值是线程的退出码

2.      线程能够被同一进程中的其它线程取消

3.      线程调用pthread_exit

pthread_exit函数:

#include<pthread.h>

voidpthread_exit(void *retval);

retval 是一个无类型的指针,与传给启动例程的单个參数类似。进程中的其它线程能够通过调用pthread_join函数訪问到这个指针。

#include<pthread.h>

intpthread_join(pthread_t thread, void **retval);

返回值:若成功则返回0,否则返回错误编号

调用pthread_join函数的线程将一直被堵塞,直到指定的线程调用pthread_exit 、从启动例程中返回或者被取消。

假设线程仅仅是从它的启动例程返回。retval 将包括返回码。假设线程被取消。retval 指定的内存单元就设置为PTHREAD_CANCELED。

#include<stdio.h>
#include<stdlib.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);
}
voiderr_quit(const char* fmt, ...)
{
    printf("%s\n",fmt);
    exit(1);
}
int main(void)
{
    int        err;
    pthread_t  tid1, tid2;
    void       *tret;

    err = pthread_create(&tid1, NULL,thr_fn1, NULL);
    if (err != 0)
    {
        err_quit("can‘t create thread 1:%s\n", strerror(err));

    }
    err = pthread_create(&tid2, NULL,thr_fn2, NULL);
    if (err != 0)
        err_quit("can‘t create thread 2:%s\n", strerror(err));
err = pthread_join(tid1, &tret);
if (err != 0)
        err_quit("can‘tjoin with thread 1: %s\n", strerror(err));
    printf("thread 1 exitcode %d\n", (int)tret);
    err = pthread_join(tid2,&tret);
    if (err != 0)
        err_quit("can‘tjoin with thread 2: %s\n", strerror(err));
    printf("thread 2 exitcode %d\n", (int)tret);
    exit(0);
}

执行及输出:

thread 2 exiting

thread 1 returning

thread 1 exit code 1

thread 2 exit code 2

能够看出当一个线程通过调用pthread_exit退出或简单地从启动例程中返回时,进程中的其它线程能够通过调用pthread_join函数获得该线程的退出状态。

pthread_exit和pthread_create函数的无类型指针參数可能传递一个复杂的结构的地址。单数该结构所使用的内存调用者完毕调用以后必须仍然是有效的。

能够使用全局结构或者malloc函数分配栈结构。

线程能够通过调用pthread_cancel函数请求取消同一进程中的其它线程。

#include <pthread.h>

int pthread_cancel(pthread_t thread);

返回值:若成功则返回0,否则返回错误编号。

在默认情况下,pthread_cancel函数会使得由thread标识的线程的行为表现为如同调用了參数为PTHREAD_CANCELED的pthread_exit函数,可是线程能够选择忽略取消方式或是控制取消方式。

线程能够安排它退出时须要调用的函数,这与进程能够用atexit函数安排进程退出时须要调用的函数时类似的。这种函数成为线程清理处理程序。

线程能够建立多个清理程序。处理程序记录在栈中。也就是说它们的运行顺序与它们注冊时的顺序相反。

#include<pthread.h>

voidpthread_cleanup_push(void (*routine)(void *), void *arg);

voidpthread_cleanup_pop(int execute);

当线程运行下面动作时调用清理函数,调用參数为arg。清理函数routine的调用顺序是由pthread_cleanup_push函数安排的。

1.调用pthread_exit时

2.响应取消请求时

3.用非零execute參数调用voidpthread_cleanup_pop时

假设execute为0,清理函数将不被调用。不管哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。

例如以下程序显示了怎样使用线程清理处理程序。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void cleanup(void *arg)
{
   printf("cleanup: %s\n", (char *)arg);
}

void * thr_fn1(void *arg)
{
   printf("thread 1 start\n");
   pthread_cleanup_push(cleanup, "thread 1 first handler");
   pthread_cleanup_push(cleanup, "thread 1 second handler");
   printf("thread 1 push complete\n");
   if (arg)
       return((void *)1);
   pthread_cleanup_pop(0);
   pthread_cleanup_pop(0);
return((void*)1);
}

void * thr_fn2(void *arg)
{
printf("thread2 start\n");
pthread_cleanup_push(cleanup,"thread 2 first handler");
pthread_cleanup_push(cleanup,"thread 2 second handler");
printf("thread2 push complete\n");
if (arg)
    pthread_exit((void *)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)2);
}

void err_quit(const char* fmt, ...)
{
printf("%s\n",fmt);
exit(1);
}

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

err =pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
err_quit("can‘t create thread 1: %s\n", strerror(err));
  err =pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
err_quit("can‘t create thread 2: %s\n", strerror(err));
err =pthread_join(tid1, &tret);
if (err != 0)
err_quit("can‘t join with thread 1: %s\n", strerror(err));
printf("thread1 exit code %d\n", (int)tret);
err =pthread_join(tid2, &tret);
if (err != 0)
err_quit("can‘t join with thread 2: %s\n", strerror(err));
printf("thread2 exit code %d\n", (int)tret);
exit(0);
}

运行及输出结果例如以下:

thread 2 start

thread 2 push complete

cleanup: thread 2 second handler

cleanup: thread 2 first handler

thread 1 start

thread 1 push complete

thread 1 exit code 1

thread 2 exit code 2

从输出结果能够看出两个线程都正确的启动和退出了,可是仅仅调用了第二个线程的清理处理程序,所以假设线程是通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用。还要注意清理程序是依照与它们安装时相反的顺序被调用的。

线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这样的情况下。原有的线程等待创建的线程结束。

仅仅有当pthread_join()函数返回时,创建的线程才算终止,才干释放自己占用的系统资源。

而分离线程不是这样子的,它没有被其它的线程所等待。自己执行结束了。线程也就终止了。立即释放系统资源,对分离状态的线程进行pthread_join的调用能够产生失败。返回EINVAL。pthread_detach调用能够用于使线程进入分离状态。

#include<pthread.h>

intpthread_detach(pthread_t thread);

返回值:若成功则返回0,否则返回错误编号。

时间: 2024-12-21 08:10:06

Linux/UNIX线程(1)的相关文章

Linux/UNIX线程控制

线程控制 线程属性 调用pthread_create函数的例子中,传入的参数都是空指针,而不是指向pthread_attr_t结果的指针.可以用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来.可以使用pthread_attr_init函数初始化pthreaad_attr_t结构.调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值.如果要修改其中个别属性的值,需要调用其他的函数.pt

Linux/Unix C编程之的perror函数,strerror函数,errno

#include <stdio.h> // void perror(const char *msg); #include <string.h> // char *strerror(int errnum); #include <errno.h> //errno ? errno 是错误代码,在 errno.h头文件中: perror是错误输出函数,输出格式为:msg:errno对应的错误信息(加上一个换行符): strerror?是通过参数 errnum (就是errno)

《Linux/Unix系统编程手册》读书笔记7 (/proc文件的简介和运用)

<Linux/Unix系统编程手册>读书笔记 目录 第11章 这章主要讲了关于Linux和UNIX的系统资源的限制. 关于限制都存在一个最小值,这些最小值为<limits.h>文件中的常量. 通过cat 命令查看: [email protected]:~/Code/tlpi$ cat /usr/include/limits.h /* Copyright (C) 1991, 1992, 1996, 1997, 1998, 1999, 2000, 2005 Free Software

Linux编程---线程

首先说一下线程的概念.其实就是运行在进程的上下文环境中的一个执行流.普通进程只有一条执行流,但是线程提供了多种执行的路径并行的局面. 同时,线程还分为核心级线程和用户级线程.主要区别在属于核内还是核外. 核心级线程,地位基本和进程相当,由内核调度.也就是说这种系统时间片是按线程来分配的.这种线程的好处就是可以适当的运用SMP,即针对多核CPU进行调度. 用户级线程,在用户态来调度.所以相对来说,切换的调度时间相对核心级线程来说要快不少.但是不能针对SMP进行调度. 对于现在的系统来说,纯粹的用户

【转】linux 用户线程、LWP、内核线程学习笔记

[好文转发---linux 用户线程.LWP.内核线程学习笔记] 在现代操作系统中,进程支持多线程.进程是资源管理的最小单元:而线程是程序执行的最小单元.一个进程的组成实体可以分为两大部分:线程集合资源集.进程中的线程是动态的对象:代表了进程指令的执行.资源,包括地址空间.打开的文件.用户信息等等,由进程内的线程共享. 线程有自己的私有数据:程序计数器,栈空间以及寄存器. Why Thread?(传统单线程进程的缺点) 1. 现实中有很多需要并发处理的任务,如数据库的服务器端.网络服务器.大容量

学习linux/unix编程方法的建议(转)

假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux系统编程=>内核开发阅读内核源码 其中学习linux常用命令时就要学会自己编译内核,优化系统,调整参数 安装和常用命令书太多了,找本稍微详细点的就ok,其间需要学会正则表达式 系统编程推荐<高级unix环境编程>,黑话叫APUE还有<unix网络编程>这时候大概还需要看资料理解e

学习linux/unix编程方法的建议,学习Linux的四个步骤(转)

解答:学习Linux的四个步骤假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux系统编程=>内核开发阅读内核源码其中学习linux常用命令时就要学会自己编译内核,优化系统,调整参数安装和常用命令书太多了,找本稍微详细点的就ok,其间需要学会正则表达式系统编程推荐<高级unix环境编程>,黑话叫APUE还有<unix网络编程>这时候

Linux/UNIX系统编程手册 PDF下载

网盘下载地址:Linux/UNIX系统编程手册 PDF下载 – 易分享电子书PDF资源网 作者: Michael Kerrisk 出版社: 人民邮电出版社 原作名: The Linux Programming Interface: A Linux and UNIX System Programming Handbook 译者: 孙剑 许从年 董健 / 孙余强 郭光伟 陈舸 出版年: 2014-1 页数: 1176 定价: 158 装帧: 平装 内容简介 · · · · · · <linux/un

Linux之线程、线程控制、线程属性

一.整体大纲 二.线程相关 1. 什么是线程    LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下) 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 区别:在于是否共享地址空间. 独居(进程):合租(线程). Linux下: 线程:最小的执行单位 进程:最小分配资源单位,可看成是只有一个线程的进程. 2. Linux内核线程实现原理     (1)线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才