Bootstrap

POSIX线程(pthread库)介绍及使用方法

一.概述

1.POXIS介绍

POXIS是一种操作系统接口标准,全称为“可移植操作系统接口”。

它最初由IEEE组织制定,目的是为了使不同的操作系统之间可以互相兼容。POSIX标准定义了一系列API(应用程序接口)和命令行工具,这些API和工具规定了操作系统应该提供哪些功能,并规定了这些功能的调用方式和行为。

POSIX标准包含多个部分,主要涵盖了文件管理、进程控制、用户权限、系统调用等方面。

跨平台特性:为了保证操作系统 API 的相互兼容性制定了 POSIX 标准。目前符合 POSIX 标准协议的操作系统有:UNIX、BSD、Linux、iOS、Android、SylixOS、VxWorks、RTEMS 等。

Windows操作系统支持POSIX。虽然Windows操作系统并不完全符合POSIX标准,但可以通过一些工具和库来实现POSIX兼容性。这些工具包括Cygwin、MinGW或MSYS等,它们提供了一些POSIX函数和命令,

使得在Windows上开发和运行POSIX兼容的应用程序成为可能。

2.POSIX线程

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准,该标准定义了创建和操纵线程的一整套API。关于pthread.h支持的平台,以下是一些主要的说明:

支持的平台

类Unix系统:

Linux:Linux系统广泛支持Pthreads,它是Linux下多线程编程的基础。通过pthread库,开发者可以在Linux环境下创建、管理和同步多个线程。

Unix:传统的Unix系统也支持Pthreads,因为它是POSIX标准的一部分。

macOS:虽然macOS是Apple公司的操作系统,但它也遵循POSIX标准,因此支持Pthreads。

Windows:

虽然Windows不是类Unix系统,但存在pthreads的移植版本,如pthreads-win32,使得Windows平台也能使用Pthreads进行多线程编程。不过,需要注意的是,

Windows本身有一套自己的线程和同步机制(如CreateThread、WaitForSingleObject等),这些机制与Pthreads有所不同。

也就是谁Windows上不能直接用pthread库,如果要用需要一些工具和库来实现POSIX兼容性,如Cygwin、MinGW或MSYS等

二.pthread具体介绍

1.pthread库

线程是操作系统能够并发执行的一个基本单元,它是进程的一个执行实例。线程与进程的区别在于,线程共享进程的地址空间,而进程之间地址空间是相互独立的。线程的创建和撤销比进程要简单,线程之间的切换也比进程之间的切换要快。

C/C++中提供了pthread库来支持线程的创建和管理。下面以linux系统下使用举例:

pthread库需要声明头文件:

#include <pthread.h>

2.线程函数

2.1pthread_create创建线程

pthread库提供了以下函数来创建线程:

int pthread_create(pthread_t *thread,

                   const pthread_attr_t *attr,

                   void *(*start_routine)(void *),

                   void *arg);

该函数用于创建一个新线程,参数thread指向新线程的标识符,attr指向线程属性,start_routine指向线程执行函数,arg指向线程执行函数的参数。

pthread_create函数如果创建线程成功,则返回值是0;否则,返回错误码。

pthread_create函数的错误码如下:

EAGAIN:系统中没有可用的线程。

EINVAL:参数attr无效。

EPERM:当前进程没有创建线程的权限。

ENOMEM:内存不足。

线程执行函数的声明如下:

void *thread_function(void *arg);

线程执行函数可以是任何C函数,但是该函数不能有返回值,并且该函数不能调用exit函数。

2.2pthread_join线程阻塞

创建线程后,可以使用pthread_join函数等待一个线程结束,并获取该线程的退出状态。(线程阻塞函数,调用该函数则等到线程结束才继续运行)该函数的声明如下:

int pthread_join(pthread_t thread, void **retval);

参数thread指向要等待的线程,retval指向一个指针,该指针将存储线程的退出状态。

如果成功,则pthread_join函数的返回值是0;否则,返回错误码。

pthread_join函数的错误码如下:

ESRCH:参数thread指向的线程不存在。

EDEADLK:当前线程尝试等待自己。

EINVAL:参数thread指向的线程已经结束。

EINTR:等待线程被中断。

下面是一个简单的线程创建和等待示例:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    printf("This is a thread.\n");

    return NULL;

}

int main() {

    pthread_t thread;

    pthread_create(&thread, NULL, thread_function, NULL);

    pthread_join(thread, NULL);

    printf("Thread finished.\n");

    return 0;

}

运行该程序,输出如下:

This is a thread.

Thread finished.

2.3pthread_exit线程退出

pthread_exit函数用于在线程中显式地退出线程,并返回一个指针值作为线程的退出状态。该函数的声明如下:

  void pthread_exit(void *retval);

参数retval是一个指针,用于传递线程的退出状态。线程的退出状态可以是任何类型的指针。 pthread_exit函数没有返回值。

使用pthread_exit函数可以在任何地方显式地终止线程的执行,并将退出状态传递给等待该线程的其他线程。

下面是一个使用pthread_exit函数的示例:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    printf("Thread is exiting.\n");

    int *result = malloc(sizeof(int));

    *result = 42;

    pthread_exit(result);

}

int main() {

    pthread_t thread;

    pthread_create(&thread, NULL, thread_function, NULL);

    

    void *exit_status;

    pthread_join(thread, &exit_status);

    

    int *result = (int *)exit_status;

    printf("Thread exited with status: %d\n", *result);

    free(result);

    

    return 0;

}

运行该程序,输出如下:

Thread is exiting.

Thread exited with status: 42

在上面的示例中,线程函数thread_function在退出之前动态分配了一个int类型的内存,并将其作为退出状态传递给pthread_exit函数。在主线程中,通过pthread_join函数获取线程的退出状态,并进行相应的处理。

2.4pthread_self获取线程标识

pthread_self函数用于获取当前线程的线程标识符。该函数的声明如下:

pthread_t pthread_self(void);

pthread_self函数的返回值是当前线程的线程标识符。 pthread_self函数的使用示例如下:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    printf("This is a thread.\n");

    pthread_t thread_id = pthread_self();

    printf("My thread id is %lu.\n", thread_id);

    return NULL;

}

int main() {

    pthread_t thread;

    pthread_create(&thread, NULL, thread_function, NULL);

    pthread_join(thread, NULL);

    return 0;

}

运行该程序,输出如下:

This is a thread.

My thread id is 140737440306016.

在上面这个例子中,创建了一个线程,并在线程中调用pthread_self函数来获取线程标识符。然后,在主线程中调用pthread_join函数来等待线程结束。当线程结束时,会在主线程中打印线程标识符。

2.5pthread_cancel取消线程

pthread_cancel函数用于向指定的线程发送取消请求,请求线程退出。被取消的线程需要在适当的地方检查取消请求,并执行相应的清理操作。 pthread_cancel函数的声明如下:

int pthread_cancel(pthread_t thread);

参数thread指向要取消的线程。

如果成功,则pthread_cancel函数的返回值是0;否则,返回错误码。 pthread_cancel函数的错误码如下:

ESRCH:参数thread指向的线程不存在

EINVAL:参数thread指向的线程已经结束

pthread_cancel函数的使用示例如下:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    printf("This is a thread.\n");

    while (1) {

        // 检查是否被取消

        if (pthread_cancel(pthread_self())) {

            // 线程被取消,执行清理操作

            break;

        }

    }

    return NULL;

}

int main() {

    pthread_t thread;

    pthread_create(&thread, NULL, thread_function, NULL);

    // 等待线程结束

    pthread_join(thread, NULL);

    return 0;

}

运行该程序,输出如下:

This is a thread.

在上面这个例子中,创建了一个线程,并在主线程中调用pthread_cancel函数来取消该线程。当线程被取消时,它会执行清理操作,然后退出。

2.6pthread_detach线程分离

pthread_detach函数用于将指定的线程标记为可分离的,这样在线程终止时,系统会自动回收线程的资源,而不需要调用pthread_join来等待线程结束。 pthread_detach函数的声明如下:

int pthread_detach(pthread_t thread);

参数thread指向要分离的线程。

如果成功,则pthread_detach函数的返回值是0;否则,返回错误码。

pthread_detach函数的错误码如下:

ESRCH:参数thread指向的线程不存在。

EINVAL:参数thread指向的线程已经结束。

pthread_detach函数的使用示例如下:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    printf("This is a thread.\n");

    return NULL;

}

int main() {

    pthread_t thread;

    pthread_create(&thread, NULL, thread_function, NULL);

    pthread_detach(thread);

    return 0;

}

运行该程序,输出如下:

This is a thread.

在上面这个例子中,创建了一个线程,并调用pthread_detach函数将该线程标记为可分离的。当线程结束时,系统会自动回收线程的资源,而不需要调用pthread_join来等待线程结束。

2.7pthread_equal线程比较

pthread_equal函数用于比较两个线程标识符是否相等。该函数的声明如下:

int pthread_equal(pthread_t t1, pthread_t t2);

参数t1和t2是两个线程标识符。

如果两个线程标识符相等,pthread_equal函数的返回值是0;否则,返回非0值。

pthread_equal函数的使用示例如下:

#include <stdio.h>

#include <pthread.h>

void *thread_function(void *arg) {

    return NULL;

}

int main() {

    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, thread_function, NULL);

    pthread_create(&thread2, NULL, thread_function, NULL);

    if (pthread_equal(thread1, thread2)) {

        printf("The two threads are equal.\n");

    } else {

        printf("The two threads are not equal.\n");

    }

    return 0;

}

运行该程序,输出如下:

The two threads are not equal.

在上面这个例子中,创建了两个线程,并使用pthread_equal函数比较它们是否相等。结果显示,两个线程是不相等的。

3.线程属性

3.1属性结构

线程的属性结构为 pthread_attr_t,同样在<pthread.h>头文件中定义,如下所示:

typedef struct pthread_attr_s

{

    int                       detachstate;     //线程的分离状态

    int                       schedpolicy;     //线程优先级

    struct sched_param        schedparam;      //线程调度参数

    int                       inheritsched;    //线程的继承性

    int                       scope;           //线程的作用域

    size_t                    guardsize;       //线程栈末尾的警戒缓冲区尺寸

    int                       stackaddr_set;   //线程的运行栈

    void *                    stackaddr;       //线程栈的地址

    size_t                    stacksize;       //线程栈的大小

}pthread_attr_t;

属性对象主要包括线程的分离状态、调度优先级、运行栈地址、运行栈大小、优先级。

但是线程的属性值不能直接设置,须使用相关函数进行操作。

3.2init初始化与destroy销毁

线程属性的初始化函数为pthread_attr_init(&attribute), 且这个函数必须在 pthread_create()函数之前调用。

线程属性的销毁函数为pthread_attr_destroy(&attribute)。

运用示例如下所示:

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

void *thread_function(void *arg) {

    printf("Thread is running\n");

    sleep(2);

    printf("Thread is exiting\n");

    pthread_exit(NULL);

}

int main() {

    pthread_t thread;

    pthread_attr_t attr;

    // 初始化线程属性

    pthread_attr_init(&attr);

    // 创建线程

    pthread_create(&thread, &attr, thread_function, NULL);

    // 等待线程

    pthread_join(thread, NULL);

    // 销毁线程属性

    pthread_attr_destroy(&attr);

    return 0;

}

3.3线程的优先级Sched

线程的优先级是经常设置的属性,由两个函数进行控制: pthread_attr_getschedparam()函数获得线程的优先级设置,函数pthread_attr_setschedparam设置线程的优先级。

int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param)

 此函数从指定的线程属性对象 attr 中检索调度参数(如优先级),并将其存储在指向sched_param 结构的 param 指针中。

- attr 参数是指向要检索调度参数的线程属性对象的指针。

- param 参数是指向 sched_param 结构的指针,用于存储调度参数。

- 函数在成功时返回 0,如果出现错误则返回错误号。

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param)

- 此函数根据 param 指向的 sched_param 结构中提供的值,在指定的线程属性对象 attr 中设置调度参数(如优先级)。

- attr 参数是指向要设置调度参数的线程属性对象的指针。

- param 参数是指向包含要设置的调度参数的 sched_param 结构的指针。

- 函数在成功时返回 0,如果出现错误则返回错误号。

线程的优先级存放在结构sched_param中,因为结构体sched_param在头文件<sched.h>中,所以要加入头文件<sched.h>。

struct sched_param {

    int sched_priority;  // Priority of the thread

    // Other scheduling parameters may be included here depending on the system

};

线程优先级的操作方式是先将优先级取出来,然后对需要设置的参数修改后再写回去,这是对复杂结构进行设置的通用办法,防止因为设置不当造成不可预料的麻烦。例如设置优先级的代码如下:

#include <stdio.h>

#include <pthread.h>

#include <sched.h>

pthread_attr_t attr;  

struct sched_param sch;

pthread_t pt;

pthread_attr_init(&attr);  /*初始化属性设置*/

pthread_attr_getschedparam(&attr, &sch);  /*获得当前的线程属性*/

sch.sched_priority = 256;  /*设置线程优先级为256*/

pthread_attr_setschedparam(&attr, &sch);  /*设置线程优先级*/

pthread_create(&pt, &attr, (void*)start_routine, &run);  /*建立线程,属性为上述设置*/

3.4线程的作用域Scope

pthread_attr_setscope() 函数是 POSIX 线程库中用于设置线程属性对象中线程作用域的函数。线程的作用域决定了线程与其他线程之间的共享资源范围。该函数的函数原型如下:

int pthread_attr_setscope(pthread_attr_t *attr, int scope);

- attr 参数是指向要设置作用域的线程属性对象的指针。

- scope 参数指定线程的作用域,可以是 PTHREAD_SCOPE_SYSTEM 或PTHREAD_SCOPE_PROCESS 。

- PTHREAD_SCOPE_SYSTEM 表示线程与整个系统共享资源,是线程的默认作用域。

- PTHREAD_SCOPE_PROCESS 表示线程与其所在的进程共享资源,线程的资源范围限定在进程内。

pthread_attr_setscope 函数用于设置线程的作用域,从而影响线程的资源共享范围。通过设置不同的作用域,您可以控制线程与其他线程之间的资源隔离程度。在多线程编程中,根据需要选择合适的线程作用域对于确保线程间资源的合理分配和管理是非常重要的。

3.5线程的分离状态Detachstate

pthread_attr_setdetachstate 函数是 POSIX 线程库中用于设置线程属性对象中线程分离状态的函数。线程的分离状态决定了线程结束时是否需要等待其他线程对其进行清理。该函数的函数原型如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

- attr 参数是指向要设置分离状态的线程属性对象的指针。

- detachstate 参数指定线程的分离状态,可以是 PTHREAD_CREATE_JOINABLE 或 PTHREAD_CREATE_DETACHED 。

- PTHREAD_CREATE_JOINABLE 表示线程是可连接的(非分离),主线程可以调用 pthread_join 来等待该线程的结束,并获取其返回值。

- PTHREAD_CREATE_DETACHED 表示线程是分离的,线程结束后资源会被自动释放,无法被其他线程等待或获取返回值。

在之前的例子中,线程建立的时候没有设置属性,默认终止方法为非分离状态。在这种情况下,需要等待创建线程的结束。只有当pthread_join()函数返回回时,线程才算终止,并且释放线程创建的时候系统分配的资源。

而分离线程不用其他线程等待,当前线程运行结束后线程就结束了,并且马上释放资源。当将一个线程设置为分离线程时,如果线程的运行非常快,可能在pthread_create()函数返回之前就终止了。

由于一个线程在终止以后可以将线程号和系统资源移交给其他的线程使用,此时再使用函数pthread_create(获得的线程号进行操作会发生错误。

线程的分离方式可以根据需要,选择适当的分离状态。

;