APUE学习之多线程编程(一):线程的创建和销毁

简介: 一、线程标识      和每个进程都有一个进程ID一样,每个线程也有一个线程ID,线程ID是以pthread_t数据类型来表示的,在Linux中,用无符号长整型表示pthread_t,Solaris 把phread_t数据类型表示为无符号整型,FreeBSD 和Mac OS X 用一个指向pthread结构的指针来表示pthread_t数据类型。
一、线程标识
     和每个进程都有一个进程ID一样,每个线程也有一个线程ID,线程ID是以pthread_t数据类型来表示的,在Linux中,用无符号长整型表示pthread_t,Solaris 把phread_t数据类型表示为无符号整型,FreeBSD 和Mac OS X 用一个指向pthread结构的指针来表示pthread_t数据类型。
     可以使用pthread_self函数获得自身的线程ID。   
#include <pthread.h>
 pthread_t pthread_self(void);
 
二、线程创建
     使用pthread_create函数创建新线程 
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_arrt_t *restrict addr, void *(*start_rtn)(void *), void *restrict arg);
     当pthread_create成功返回后,新创建线程的线程ID会被设置成tidp指向的内存单元,attr参数用于定制各种不同的线程属性,后面再讨论线程属性,现在先把它置为null,创建一个具有默认属性的线程。
     新创建的线程从start_rtn函数开始运行,该函数接收一个无类型指针的参数arg,如果要传给它的参数多于一个,可以把参数放到一个结构中,然后把结构的地址作为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);
}
View Code
  这个程序有两个特别的地方:第一,主线程需要休眠,如果主线程不休眠,主线程会退出,新线程并没有机会运行。第二,新线程通过pthread_self(),获得自己的线程ID。
./a.out
main thread:  pid 27335 tid 3076404928 (0xb75e36c0)
new thread:  pid 27335 tid 3076401984 (0xb75e2b40)
View Code
  虽然Linux线程ID是用无符号长整型来表示的,但它们看起来更像指针。
 
三、线程终止
     如果任意线程调用了exit,_exit,_Exit,整个进程都会终止,这个要注意。
     单个线程可以通过以下三种方式退出,且不终止整个进程。
     1.线程可以简单地从启动例程中返回,返回值是线程的退出码。
     2.线程可以被同一进程中的其他线程取消。
     3.调用pthread_exit
 
     先来看pthread_exit退出的情况。
#include <pthread.h>
void pthread_exit(void *rval_ptr);

     ravl_ptr是无类型指针,进程中的其他线程可以通过pthread_join函数获得这个指针。

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

     调用线程将一直阻塞,直至指定的线程退出,rval_ptr就包含返回码,如果线程被取消,rval_ptr指定的内存单元就设置为PTHREAD_CANCELED.可以通过调用pthread_join自动把线程置于分离状态,如果线程已处于分离状态,pthread_join就会调用失败。

例子:
#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 thread1");
    }
 
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
 
    if (err != 0)
    {
        err_exit(err, "can't create thread2");
    }
 
    err = pthread_join(tid1, &tret);
 
    if (err != 0)
    {
        err_exit(err, "can't join thread1");
    }
 
    printf("thread1 exit code:%ld\n", (long)tret);
 
    err = pthread_join(tid2, &tret);
 
    if (err != 0)
    {
        err_exit(err, "can't join thread2");
    }
 
    printf("thread2 exit code:%ld\n", (long)tret);
 
    return 0;
}
View Code
./a.out
thread 2 exiting
thread 1 returning
thread1 exit code:1
thread2 exit code:2
View Code
  也可传递包含复杂消息的结构的地址,不过必须注意,这个结构所使用的内存必须在完成调用后仍是有效的。
      线程也可以调用pthread_cancel函数来请求取消同一进程的其他线程
#include <pthread.h>
int pthread_cancel(pthread_t tid);

     听着有点霸道,不过也只是请求而已,线程可以选择忽略这个请求。

     线程可以安排它退出时需要调用的函数,这样的函数是由pthread_cleanup_push注册在栈中的,所以执行顺序与注册时相反。
#include <pthread.h>
void pthread_cleanup_push(void(*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);

     当线程执行以下动作时,清理函数rtn由pthread_cleanup_push函数调度

     1.调用pthread_exit时
     2.响应取消请求时
     3.用非零execute参数调用pthread_cleanup_pop时。
例子:
#include "apue.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("thread 2 start\n");
    pthread_cleanup_push(cleanup, "thread 2 first handler");
    pthread_cleanup_push(cleanup, "thread 2 second handler");
    printf("thread 2 push complete\n");
 
    if (arg)
    {
        pthread_exit((void *)2);
    }
 
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
 
    return (void *)2;
}
 
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
 
    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
    {
        err_exit(err, "can't create thread 1");
    }
 
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    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);
 
    return 0;
}
View Code 
./a.out
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
View Code
     可知如果线程是通过它的启动例程中返回而终止的话,它的清理处理程序就不会被调用。
 
     在默认情况下,线程的终止状态会一直保存到对该线程调用pthread_join,如果该线程已经被分离,则底层的资源可以在线程终止时立即被收回,不用再调用pthread_join,且再调用pthread_join会出错。
#include <pthread.h>
int pthread_detach(pthread_t tid);
目录
相关文章
|
8天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
25 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
3天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
16 4
|
11天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
2天前
|
安全 Java 调度
Java 并发编程中的线程安全和性能优化
本文将深入探讨Java并发编程中的关键概念,包括线程安全、同步机制以及性能优化。我们将从基础入手,逐步解析高级技术,并通过实例展示如何在实际开发中应用这些知识。阅读完本文后,读者将对如何在多线程环境中编写高效且安全的Java代码有一个全面的了解。
|
10天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
31 10
|
12天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
2天前
|
安全 数据库连接 API
C#一分钟浅谈:多线程编程入门
在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
16 0
|
11天前
|
安全 Java UED
Java并发编程:解锁多线程的潜力
在Java的世界里,并发编程如同一场精心编排的交响乐,每个线程扮演着不同的乐手,共同奏响性能与效率的和声。本文将引导你走进Java并发编程的大门,探索如何在多核处理器上优雅地舞动多线程,从而提升应用的性能和响应性。我们将从基础概念出发,逐步深入到高级技巧,让你的代码在并行处理的海洋中乘风破浪。
|
25天前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
49 1
|
17天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。