Unix/Linux编程:POSIX线程的创建与退出

POSIX线程库

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”开头,要使用这些函数库,要通过引入头文<pthread.h>,而且链接这些线程函数库时要使用编译器命令的“-lpthread”选项[Ubuntu系列系统需要添加的是”-pthread”选项而不是”-lpthread”,如Ubuntu 14.04版本,深度Ubuntu等]

创建线程

pthread_create

启动程序时,产生的进程只有单条线程,称之为初始(initial)或主(main)线程函数 pthread_create()负责创建一条新线程

  • 新线程通过调用带有参数 arg 的函数 start_routine(即 start_routine(arg))而开始执行。调用 pthread_create()的线程会继续执行该调用之后的语句
  • 将参数 arg 声明为 void*类型,意味着可以将指向任意对象的指针传递给 start_routine()函数。一般情况下,arg 指向一个全局或堆变量,也可将其置为 NULL。如果需要向 start()传递多个参数,可以将 arg 指向一个结构,该结构的各个字段则对应于待传递的参数。通过审慎的类型强制转换,arg 甚至可以传递 int 类型的值
  • start_routine的返回值类型为 void*,对其使用方式与参数 arg 相同。将经强制转换的整型数作为线程 start 函数的返回值时,必须小心谨慎。原因在于:
    • 取消线程时的返回值PTHREAD_CANCELED,通常是由实现所定义的整形值。再经强制转换为void*。
    • 如果线程甲的start_routine函数将此整型值返回给正在执行pthread_join()操作的线程乙,乙会误认为甲遭到了取消。
  • 参数 thread 指向 pthread_t 类型的缓冲区,在 pthread_create()返回前,会在此保存一个该线程的唯一标识。
    • 后续的 Pthreads 函数将使用该标识来引用此线程。
    • SUSv3 明确指出,在新线程开始执行之前,实现无需对 thread 参数所指向的缓冲区进行初始化,即新线程可能会在pthread_create()返回给调用者之前已经开始运行。
    • 如新线程需要获取自己的线程 ID,则只能使用pthread_self()方法。
  1. 线程创建时并不能保证哪个线程先运行:是新创建的线程/调用线程,这取决于内核的调度。
  2. 新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除
  3. 每个线程都有一个在当前进程中的独一无二的线程ID,我们可以通过pthread_self来获取它
NAME
       pthread_create - 创建一个新的线程

SYNOPSIS
       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

      编译并链接 -pthread.

DESCRIPTION
       pthread_create() 函数的作用是:在调用过程中启动一个新线程。新线程通过调用 start_routine() 开始执行;arg 
      作为 start_routine() 的唯一参数传递。

      新线程以下列方式之一终止:

       *  它调用 pthread_exit(3),指定一个退出状态值,该值可供调用 pthread_join(3) 的同一进程中的另一个线程使用

       * 它从 start_routine() 返回。 这等效于使用 return 语句中提供的值调用 pthread_exit(3)* 它被取消(参见 pthread_cancel(3)* 进程中的任何线程调用exit(3),或者主线程执行main()的返回。 这会导致进程中的所有线程终止。 

       attr:   设置[线程的属性],一般设置为NULL表示使用默认属性;可以使用 pthread_attr_init(3) 和相关函数进行初始化

       thread: 在返回之前,成功调用 pthread_create() 会将[新线程的ID]存储在 thread 指向的缓冲区中; 此标识符用于
       			在后续调用其他 pthreads 函数时引用该线程。	
	
	   start_routine: 是个函数地址,线程启动后要执行的函数
		
	  arg:  传给线程启动函数的参数

       新线程继承创建线程的信号掩码 (pthread_sigmask(3)) 的副本。 新线程的挂起信号集为空(sigpending(2))。 新线程不
       继承创建线程的备用信号堆栈 (sigaltstack(2))。

       新线程继承调用线程的浮点环境 (fenv(3))。

       新线程的 CPU 时间时钟的初始值为 0(请参阅 pthread_getcpuclockid(3))。 

   特定于 Linux 的详细信息
        新线程继承调用线程的能力集(请参阅capabilities(7))和CPU 关联掩码(请参阅sched_setaffinity(2))的副本。 

RETURN VALUE
        成功返回0;失败返回错误码,并且 *thread 的内容是未定义的。 

	附-Posix错误检查
		*  UNIX传统的函数:成功返回0,失败返回-1,并且对设置全局变量errno以指定错误类型。然而pthreads函数出错时不会
		   设置全局变量errno(而其他的大部分POSIX函数会设置errno)。而是将错误代码通过返回值返回;
		*  pthreads同样也提供了线程内的errno变量,对于每一个线程, 都有一个errno的值, 以支持其它使用errno的代码。
			对于pthreads函数的错误,<font color=red>建议通过返回值进行判定</font>,因为读取返回值要比读取线程内
			的errno变量的开销更小!

ERRORS
       EAGAIN   资源不足,无法创建另一个线程,或者遇到系统对线程数施加的限制。 
       			后一种情况可能以两种方式发生:
       			      达到 RLIMIT_NPROC 软资源限制(通过 setrlimit(2) 设置),它限制了真实用户 ID 的进程数; 
       			      或者达到了内核对线程数的系统范围限制,/proc/sys/kernel/threads-max。 

       EINVAL attr 中的设置无效。 

       EPERM  无权设置 attr 中指定的调度策略和参数。 

NOTES
       有关 pthread_create()*thread 中返回的线程 ID 的更多信息,请参阅 pthread_self(3)。除非采用实时调度
       策略,否则在调用 pthread_create() 之后,下一次执行哪个线程(调用者或新线程)是不确定的。

       一个线程可以是可连接的(joinable),也可以是分离的(detached)。
       		
       		如果一个线程是joinable,那么另一个线程可以调用 pthread_join(3) 来等待线程终止并获取其退出状态。只有当一个
       终止的可连接线程被joined,它的最后一个资源才会释放回系统。
       		
       		当一个detached的线程终止时,它的资源会自动释放回系统:不可能join线程以获得它的退出状态。

			使线程分离对于某些类型的守护线程非常有用,它们的退出状态应用程序不需要关心。

			默认情况下,会在可连接状态下创建新线程,除非将 attr 设置为在分离状态下创建线程(使用 pthread_attr_setdetachstate(3))。

       在 Linux/x86-32 上,新线程的默认堆栈大小为 2 兆字节。 在 NPTL 线程实现下,如果程序启动时的 RLIMIT_STACK 软资源
       限制具有“无限制”以外的任何值,则它决定了新线程的默认堆栈大小。 使用 pthread_attr_setstacksize(3),可以在用于创建
       线程的 attr 参数中显式设置堆栈大小属性,以获得不同于默认值的堆栈大小。 

pthread_self、pthread_equal

理论

作用:
* 返回线程ID
pthread_t pthread_self(void);


NAME
       pthread_self - 获取调用线程的ID 

SYNOPSIS
       #include <pthread.h>

       pthread_t pthread_self(void);

      编译并链接  -pthread.

DESCRIPTION
       pthread_self() 函数返回调用线程的 ID。 这与创建该线程的pthread_create(3)调用中的* thread返回的值相同。

	  进程ID是`pid_t`,线程ID是`pthread_t ` 

RETURN VALUE
       此函数总是成功,返回调用线程的 ID。 

ERRORS
       此函数总是成功

进程内部的每个线程都有一个唯一标识,称为线程 ID。线程 ID 会返回给 pthread_create()的调用者,一个线程可以通过 pthread_self()来获取自己的线程 ID

函数 pthread_equal()可检查两个线程的 ID 是否相同


NAME
       pthread_equal - 比较线程ID 

SYNOPSIS
       #include <pthread.h>

       int pthread_equal(pthread_t t1, pthread_t t2);

       Compile and link with -pthread.

DESCRIPTION
       pthread_equal() 函数比较两个线程标识符。 

RETURN VALUE
      如果两个线程 ID 相等,则 pthread_equal() 返回一个非零值; 否则返回 0。 

ERRORS
       此函数总是成功

例如,为了检查调用线程的线程ID 与保存于变量t1 中的线程ID 是否一致,可以编写如下代码:

if(pthread_equal(tid, pthread_self())){
	printf("match");
}

因为必须将 pthread_t 作为一种不透明的数据类型加以对待,所以函数 pthread_equal()是必须的。Linux 将 pthread_t 定义为无符号长整型(unsigned long),但在其他实现中,则有可能是一个指针或结构。

SUSv3 并未要求将 pthread_t 实现为一个标量(scalar)类型,该类型也可以是一个结构。因此,下列显示线程 ID 的代码实例并不具有可移植性(尽管该实例在包括 Linux 在内的许多实现上均可正常运行,而且有时在调试程序时还很实用)。

pthread_t thr;

printf("thread id = %ld\n", (long) thr);

在 Linux 的线程实现中,线程 ID 在所有进程中都是唯一的。不过在其他实现中则未必如此,SUSv3 特别指出,应用程序若使用线程 ID 来标识其他进程的线程,其可移植性将无法得到保证。此外,在对已终止线程施以 pthread_join(),或者在已分离(detached)线程退出后,实现可以复用该线程的线程 ID

注意:POSIX线程ID于Linux专有的系统调用gettid()所返回的线程ID并不相同。POSIX线程ID由线程库来分配和维护。gettid()返回的线程ID是一个由内核分配的数字,类似于进程ID。虽然在Linux NPTL 线程实现中,每个 POSIX 线程都
对应一个唯一的内核线程 ID,但应用程序一般无需了解内核线程 ID(况且,如果程序依赖于这一信息,也将无法移植)。

实践

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <zconf.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){
        fprintf(stderr, "can't create thread");
        exit(0);
    }

    printids("main thread:");
    sleep(1);
    printf("%s pid %lu , new tid %lu (0x%lx)\n", "in main : ", (unsigned long)getpid(),(unsigned long)ntid, (unsigned long)ntid);
    exit(0);
}

在这里插入图片描述

  • pthread_create成功返回时,第一个参数tidp执行的内存单元就是新创建的线程ID
  • 还可以通过pthread_self来获取当前线程的ID
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int main(int argc,char *argv[]){
    pid_t pid=fork();
    printf("0: main thread(%lu) , process(%d)\r\n",pthread_self(), getpid());
    if ( pid < 0 ) {
        fprintf(stderr,"错误!");
    } else if( pid == 0 ) {
        printf("2: child thread(%lu), process(%d) \r\n",pthread_self(), getpid());
        printf("子进程空间\n");
        exit(0);
    } else {
        printf("1: main thread(%lu) , process(%d) \r\n",pthread_self(), getpid());
        printf("父进程空间,子进程pid为%d\n",pid);
    }
    // 可以使用wait或waitpid函数等待子进程的结束并获取结束状态
    exit(0);
}

在这里插入图片描述

pthread_once

理论

其实在开发中,很多事情都仅仅需要做一次,不管是什么。在主函数中并且在调用任何其他依赖于初始化的事物之前初始化应用是最为容易的,特别是在创建任何线程之前初始化它需要的数据,如 互斥量、条件变量、线程特定数据键等。

extern int pthread_once (pthread_once_t __once_control, void (__init_routine) (void)) __nonnull ((1, 2));

根据该函数的定义:

①首先,需要声明类型为 pthread_once_t 的一个控制变量,而且该控制变量必须使用PTHREAD_ONCE_INIT宏来静态的初始化。

②必须创建一个包含与该 “控制变量(pthread_once_t)” 相关联的所有初始化代码函数,现在线程可以在任何时间调用pthread_once,指定一个指向一个控制变量的指针和指向相关初始化函数的指针。

pthread_once函数首先检查控制变量,以判断是否已经完成初始化。如果已经完成,pthread_once简单的返回;否则,它回去调用初始化函数(没有参数),并且记录下初始化被完成。如果在一个线程初始化的时候,另外一个线程也调用了pthread_once,则调用线程将阻塞等待,知道那个线程完成初始化后返回。换言之,当调用pthread_once成功返回时,调用总是能够肯定所有的状态已经初始化完成。

参见

实践


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <zconf.h>


static int  __init_once_key = 0;
static void init_once(void){
    const char *myname = "init_once";

    __init_once_key++;
    printf("%s(%d, tid=%d): __init_once_key=%d\n",
           myname, __LINE__, (int) pthread_self(), __init_once_key);
}

#  define	acl_unused	__attribute__ ((__unused__))
static pthread_once_t  once_control = PTHREAD_ONCE_INIT;
static void *test_once_thread(void *arg acl_unused){
    pthread_once(&once_control, init_once);
    // init_once();  // 注释这一句或者上一句
    return NULL;
}

static void test_pthread_once(void){
    pthread_attr_t attr;
    pthread_t t1, t2;

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 10240000);
    pthread_create(&t1, &attr, test_once_thread, NULL);
    pthread_create(&t2, &attr, test_once_thread, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf(">>> test_pthread_once: over now, enter any key to exit...\n");
}
int main(void)
{
    test_pthread_once();
    getchar();
}


在这里插入图片描述
在这里插入图片描述

退出线程

如何从线程中退出进程?

  • 如果进程中的任意线程调用了exit_Exit_exit,整个进程就会终止
  • 主线程执行了 return 语句(在 main()函数中),整个进程就会终止
  • 如果线程中某个信号的默认动作是终止进程,那么,发送到某个线程的信号就会终止整个进程

如何在线程中退出线程而不终止进程?

  • 线程函数start_routine执行 return 语句并返回指定值
  • 线程调用pthread_exit
  • 线程可以被同一进程中的其他线程取消(pthread_cancel

pthread_exit

NAME
       pthread_exit - 线程调用此函数终止自己

SYNOPSIS
       #include <pthread.h>

       void pthread_exit(void *retval);

       编译并链接  -pthread.

DESCRIPTION
       pthread_exit()函数将终止调用线程,且其返回值retval可由另一线程通过调用 pthread_join()来获取(如果线程是
       可连接的)
       
       retval:指向该线程的返回值;注意:retval不能指向一个局部变量。

       任何由 pthread_cleanup_push(3) 建立但尚未弹出的清理处理程序都将弹出(与推送它们的顺序相反)并执行。 如果线程
       有任何线程特定的数据,则在执行清理处理程序后,会以未指定的顺序调用相应的析构函数。 

       当线程终止时,进程共享资源(例如互斥锁、条件变量、信号量和文件描述符)不会被释放,并且不会调用使用 atexit(3) 
       注册的函数。
        
       在进程中的最后一个线程终止后,该进程通过调用 exit(3) 以退出状态为零来终止; 因此,进程共享资源被释放,使用
       atexit(3) 注册的函数被调用。 

RETURN VALUE
       此函数不会返回给调用者。 

ERRORS
       此功能总是成功。 

当调用这个函数之后,父线程会等待其他所有的子线程终止,之后父线程自己终止

  • 调用pthread_exit()相当于在线程的start_routine函数中执行return,不同之处在于,可以在线程start_routine函数所调用的任一函数中调用pthread_exit()
  • 参数retval指定了线程的返回值,retval所指向的内容不应分配在线程栈中,因为线程终止后,将无法确定线程栈的内容是否有效(系统可能会立即将该进行虚拟内存的这片区域重新分配,供一个新线程栈使用)(出于同样的理由,start_routine()没有返回值)
  • 如果主线程调用了pthread_exit(),而非调用exit()或者是执行 return 语句,那么其他线程将继续运行

也可以通过调用 pthread_cancel 来主动终止一个子线程,和 pthread_exit 不同的是,它可以指定某个子线程终止。

int pthread_cancel(pthread_t tid)

pthread_join(线程阻塞等待终止)

pthread_join可以理解为用来连接已终止的线程:pthread_join()等待thread标识的线程终止,如果线程已经终止,那么就立即返回。这种操作被称为连接(joining)。

(我们可以通过调用 pthread_join 回收已终止线程的资源,当调用 pthread_join 时,主线程会阻塞,直到对应 thread的子线程自然终止。和 pthread_cancel 不同的是,它不会强迫子线程终止)

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

NAME
       pthread_join - 阻塞等待线程结束

SYNOPSIS
       #include <pthread.h>

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

       编译并链接 -pthread.

DESCRIPTION
	 * thread:指定等待的线程的ID
	 
	 * retval:
	 
	 	* 指向一个指针,后者指向线程的返回值(用户获取线程的返回值)

	 	* 如果对线程的返回值不感兴趣,可以令 retval= NULL。这样,当线程终止时,就不会获取线程的终止状态

	 	* 如果线程被取消,value_ptr = PTHREAD_CANCELED

	 	* 若 retval 为一非空指针,将会保存线程终止时返回值的拷贝,该返回值亦即线程调用returnpthred_exit()时所指定的值。

返回值:
		成功返回0;失败返回错误编号

如果线程并未分离(detached),则必须使用pthread_join()来连接。如果未能连接,那么线程终止时将产生僵尸线程,与僵尸进程类似,除了浪费系统资源外,僵尸线程如果累积过多,僵尸线程若累积过多,应用将再也无法创建新的线程。

  • 调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回(函数结束)或者被取消
  • 调用pthread_join等待某一线程,被等待的线程结束之后就会被置于分离状态,这样线程所使用的资源就可以恢复
  • 如果调用pthread_join等待一个线程时,如果线程已处于分离状态(例如调用pthread_detach函数),pthread_join调用会失败,返回EINVAL,尽管这种行为是与具体实现相关的

pthread_join执行的功能类似waitpid,不过二者之间存在一些显著区别

  • 线程之间的关系是对等的。进程中的任一线程均可以调用pthread_join()与该进程的任何其他线程链接起来。这与进程间的层次关系不同,父进程如果fork()了子进程,那么它也是唯一能够对子进程调用wait()的进程
  • 无法“连接任意线程”(对于进程,则可以通过调用 waitpid(-1, &status, options)做到这一点),也不能以非阻塞(nonblocking)方式进行连接(类似于设置 WHOHANG 标志的 waitpid())。使用条件(condition)变量可以实现类似的功能

pthread_detach

NAME
       pthread_detach - 分离线程 

SYNOPSIS
       #include <pthread.h>

       int pthread_detach(pthread_t thread);

       Compile and link with -pthread.

DESCRIPTION
       pthread_detach() 函数将thread标识的线程标记为已分离。 当一个分离的线程终止时,它的资源会自动释放回系统,
       而无需另一个线程与终止的线程连接。

       尝试分离已分离的线程会导致未指定的行为 

RETURN VALUE
       成功时,返回0;出错时,它返回一个错误号。 

默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过调用pthread_join()获取其返回状态。有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动清理并移除置,这时,可以使用pthread_join将线程标记为detached状态

如下,使用pthread_detache,线程可以自行分离:

pthread_detach(pthread_self());

一旦线程出于分离状态,就不能再使用pthread_join来获取其状态,也无法使其重返可连接状态

其他线程调用了exit()或者主线程return,即使遭到分离的线程也还是会受到影响。此时,不管线程是可连接状态还是可分离状态,进程的所有线程会立即终止。换言之,pthread_detach()只是控制线程终止之后所发生的事情,而非何时或如何终止线程

总结:

  • 将一个线程分离:如果在新创建的线程结束时主线程没有结束同时也没有调用pthread_join,则会产生僵线程,此问题可以通过设置线程为分离的(detach)来解决;
  • 默认情况下,线程的终止状态会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被收回。在线程被分离之后,我们不能用pthread_join函数等待它的终止状态。

例子:

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


int main(void)
{
    pthread_exit(0);   // 
    getchar();
}

  • 效果:整个程序结束
#include <stdlib.h>


void* thread_func(void* arg)
{
    printf("thread:%lu is running\n", pthread_self());
    pthread_exit(0);
    printf("thread:%lu is end\n", pthread_self());
}
int main()
{
    pthread_t thid;
    
    pthread_create(&thid, NULL, thread_func, NULL);
    printf("main thread begin join\n");
    pthread_join(thid, NULL);
    printf("main thread end join\n");
    
    getchar();

    return 0;
}

效果:子进程调用pthread_exit不会导致程序结束
在这里插入图片描述

线程取消

Unix/Linux编程:线程取消

线程属性

理论

  #include <pthread.h>

typedef struct
{
       int                       detachstate;   // 线程的分离状态
       int                       schedpolicy;   // 线程调度策略
       structsched_param         schedparam;    // 线程的调度参数
       int                       inheritsched;  // 线程的继承性
       int                       scope;         // 线程的作用域
       size_t                    guardsize;     // 线程栈末尾的警戒缓冲区大小
       int                       stackaddr_set; // 线程的栈设置
       void*                     stackaddr;     // 线程栈的位置
       size_t                    stacksize;     // 线程栈的大小
} pthread_attr_t;
  /*
* 功能: pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现 支持的线程所有属性的默认值
* 返回值: 若成功返回0,若失败返回-1。
*/
 int pthread_attr_init(pthread_attr_t *attr);
   /*
* 功能:   如果pthread_attr_init实现时为属性对象分配了动态内存空间,
                pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经
                pthread_attr_destroy去除初始化之后的pthread_attr_t结构被
                pthread_create函数调用,将会导致其返回错误。
* 返回值: 若成功返回0,若失败返回-1。
*/
int pthread_attr_destroy(pthread_attr_t *attr);   
                
  1. 线程的分离状态:
  • 线程的分离状态决定一个线程以什么样的方式来终止自己
  • 在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join函数返回时,创建的线程才算终止,才能释放自己占用的资源
  • 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
  • 通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是if有一 些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我
    们就无须等待管理,只要线程自己结束了,自己释放src就可以
 #include <pthread.h>
 /*
 * 参数: attr:线程属性变量
 *        detachstate:分离状态属性  
 * 				 detachstate参数为:PTHREAD_CREATE_DETACHED    分离状态启动
 * 							PTHREAD_CREATE_DETACHED 则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源
 * 							一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态
 * 				 detachstate参数为:PTHREAD_CREATE_JOINABLE    默认启动线程
 * 返回值: 若成功返回0,若失败返回-1
*/
int pthread_attr_getdetachstate(const pthread_attr_t * attr, int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);
  1. 线程的继承性
#include <pthread.h>
/*
* 参数:    attr                线程属性变量
* 		   inheritsched     线程的继承性
* 				PTHREAD_INHERIT_SCHED    新的线程继承创建线程的策略和参数
* 				PTHREAD_EXPLICIT_SCHED   新的线程继承策略和参数来自于schedpolicy和schedparam属性中显式设置的调度信息!
* 返回值:  若成功返回0,若失败返回-1。
*/
int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
  1. 线程的调度策略
#include <pthread.h>
/*
* 参数:  attr     线程属性变量
* 		 policy    调度策略   
* 				      SCHED_FIFO    :先进先出, 允许被高优先级抢占,  也就是有高优先级的必须先运行
* 					  SCHED_RR       :轮转法
* 				      SCHED_OTHER    :其他方法
* 							 SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR支持优先级的使用,他们分别为1和99,数值越大优先级越高
* 							 当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。
* 返回值: 若成功返回0,若失败返回-1
* 				
*/
int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
  1. 线程的调度参数
 #include <pthread.h>
 struct sched_param {
      int sched_priority;    //!> 参数的本质就是优先级, 大的权值对应高的优先级
};
/*
* 返回值: 若成功返回0,若失败返回-1
*/
int pthread_attr_getschedparam(const pthread_attr_t *,struct sched_param *);
int pthread_attr_setschedparam(pthread_attr_t *,const struct sched_param *);

/*
*  系统支持的优先级的最大值
*/
 int sched_get_priority_max( int policy );
 /*
*   系统支持的优先级的最小值
*/
  int sched_get_priority_min( int policy );
  1. 线程的作用域
#include <pthread.h>   
/*
* 参数:  attr     线程属性变量
*         scope    线程的作用域 
* 						 PTHREAD_SCOPE_PROCESS(进程内竞争资源)
* 						 PTHREAD_SCOPE_SYSTEM   (系统级竞争资源)
* 返回值: 若成功返回0,若失败返回-1
* 				
*/
int    pthread_attr_getscope( const pthread_attr_t * attr, int * scope );
int pthread_attr_setscope( pthread_attr_t*, int scope );
  1. 线程堆栈管理
  • 对于进程来讲,虚地址空间的大小是固定的,因为进程中只有一个栈。但是对于线程来讲,同样大小的虚地址空间必须被所有的线程栈共享。如果应用程序用了很多线程,以致这些线程栈的累计大小超过了可用的虚地址空间,就需要减少默认的线程栈的大小。另一方面,如果线程调用的函数分配了大量的自动变量,或者调用的函数涉及了很深的栈帧,那么需要的栈大小可能比默认大
  • 如果线程栈的虚地址空间用完了,可以使用mallocnmap来为可替代的栈分配空间,并且使用pthread_attr_setstack来改变新建线程的栈位置。
 #include <pthread.h>


/*
* 功能: 线程堆栈地址
* 注意:pthread_attr_getstackaddr已经过期,现在使用的是:pthread_attr_getstack
*/
int pthread_attr_getstackaddr(const pthread_attr_t *attr,void **stackaddf);
 int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);

/*
* 
*/
int pthread_attr_getstack (const pthread_attr_t *__restrict __attr,
				  void **__restrict __stackaddr,
				  size_t *__restrict __stacksize)
/*
* 参数: __stackaddr 指定可以用作线程栈的内存范围中的最低可寻址地址(如果栈是从高地址到低地址增长的,那么__stackaddr是栈的结尾位置)
*/
int pthread_attr_setstack (pthread_attr_t *__attr, void *__stackaddr,
				  size_t __stacksize) 


/*
* 功能:  线程堆栈大小(如果只希望改变默认栈大小,又不想自己处理线程栈的分配问题)
* 参数:  attr     线程属性变量(不能小于PTHREAD_STACK_MIN)
* 		 stacksize        堆栈大小
* 返回值: 若成功返回0,若失败返回-1 
* */
 int pthread_attr_getstacksize(const pthread_attr_t *,size_t * stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);
  1. 警戒缓冲区
#include <pthread.h>
/*
* 功能: 用来设置和得 到线程栈末尾的警戒缓冲区大小。
* 返回值: 若成功返回0,若失败返回-1 
* 注意:   线程属性guardsize控制着线程栈末尾之后以避免栈溢出的扩展内存大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线程属性设为0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓存区。同样地,如果对线程属性stackaddr作了修改,系统就会认为我们会自己管理栈,并使警戒栈缓冲区机制无 效,等同于把guardsize线程属性设为0。
*/
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr ,size_t *guardsize);

实践

#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <zconf.h>

void * thr_fn(){
    printf("thread run\n");
    pthread_exit((void *)0);
}

int main(void)
{
    pthread_t tid;
    pthread_attr_t attr;
    int ret ;


    ret = pthread_attr_init(&attr);
    if(ret!=0){
        printf("init attr error:%s\n",strerror(ret));
    }

    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if(ret!=0){
        printf("pthread_attr_setdetachstate error:%s\n",strerror(ret));
    }

    ret = pthread_create(&tid,&attr,thr_fn,NULL);
    if(ret!=0){
        printf("create thread error:%s\n",strerror(ret));
    }

    pthread_attr_destroy(&attr);
    sleep(1);
}

封装一下:

#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <zconf.h>

void * thr_fn(){
    printf("--- run\n");
    pthread_exit((void *)0);
}

int makethread(void *(*fn)(void *), void *arg)
{
    int				err;
    pthread_t		tid;
    pthread_attr_t	attr;

    err = pthread_attr_init(&attr);
    if (err != 0)
        return(err);
    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (err == 0)
        err = pthread_create(&tid, &attr, fn, arg);
    pthread_attr_destroy(&attr);
    return(err);
}
int main(void)
{
    makethread(thr_fn(), NULL);
    sleep(1);
}

进程API VS 线程API

j进程线程描述
forkpthread_create创建新的控制流
exitpthread_exit从现有控制流中退出
waitpidpthread_join从控制流中得到退出状态
atexitpthread_cancal_push注册在退出控制流时调用的函数
getpidpthread_self获取控制流的ID
abortpthread_cancel请求控制流的非正常退出

实践

获取已终止的线程的退出码

#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);
}

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

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    if (err != 0){
        fprintf(stderr, "can't create thread 1");
        exit(err);
    }

    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0){
        fprintf(stderr, "can't create thread 2");
        exit(err);
    }

    err = pthread_join(tid1, &tret);
    if (err != 0){
        fprintf(stderr, "can't join with thread 1");
        exit(err);
    }

    printf("thread 1 exit code %ld\n", (long)tret);
    err = pthread_join(tid2, &tret);
    if (err != 0){
        fprintf(stderr, "can't join with thread 2");
        exit(err);
    }

    printf("thread 2 exit code %ld\n", (long)tret);
    exit(0);
}

在这里插入图片描述

在这里插入图片描述

#include <pthread.h>
#include <string.h>
#include <stdio.h>
typedef struct THREAD_CTX {
    int   i;
} THREAD_CTX;



static void *test_thread_fn(void *arg)
{
    THREAD_CTX *ctx = (THREAD_CTX*) arg;

    ctx->i = 1000;
    printf("current tid is: %lu\r\n", (unsigned long int) pthread_self());


    return (ctx);
}
static void test_thread(void)
{
    pthread_t tid;
    pthread_attr_t attr;
    THREAD_CTX ctx, *pctx;
    void *return_arg;
    unsigned int id;



    ctx.i = 0;
    pthread_attr_init(&attr);
    pthread_create(&tid, &attr, test_thread_fn, &ctx);
    pthread_join(tid, (void**) &return_arg);
    pctx = (THREAD_CTX*) return_arg;
    memcpy(&id, &tid, sizeof(id));
    printf("ctx.i=%d, pctx->i=%d, the thread id is: %u\r\n",
           ctx.i, pctx->i, id);
}
int main(void)
{
    test_thread();
    return 0;
}




不正确的退出码使用

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <zconf.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){
        fprintf(stderr, "can't create thread 1");
        exit(err);
    }

    err = pthread_join(tid1, (void *)&fp);
    if (err != 0){
        fprintf(stderr, "can't join with thread 1");
        exit(err);
    }


    printf("\n");
    printfoo("parent(after thread1 exit):\n", fp);

    printf("\n");
    sleep(1);
    printf("parent starting second thread\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    if (err != 0){
        fprintf(stderr, "can't create thread 2");
        exit(err);
    }

    sleep(1);
    printfoo("parent:\n", fp);
    exit(0);
}

在这里插入图片描述

在这里插入图片描述
pthread_joinpthread_exitvalue_ptr同一块内存, 但是这个内存在调用者完成之后必须是有效的。常见错误车间:

  • 在调用线程的栈上分配了该结构,其他线程在使用这个内存时其中内容可能已经变了
  • 线程在自己栈上分配了一个结构,然后把指向这个结构的指针传给了pthread_exit,那么调用pthread_join时,这个栈有可能被撤销,这块内存被用在其他地方了

向子线程传递参数

一个demo:

#include <iostream>
#include <pthread.h>
#include <cstring>


//*************主控线程向子线程传递数据 【通过创建线程时参数】******************//
typedef struct _Student{
    char name[20];
    unsigned int age;
}Student;

void *threadFunction(void *args){
    std::cout << "In Thread: " << pthread_self() << std::endl;
    Student tmp = *(Student *)(args);
    std::cout << "Name: " << tmp.name << std::endl;
    std::cout << "Age: " << tmp.age << std::endl;
    pthread_exit(NULL);
}
int main() {

    Student student = {"xiaofang",22};

    pthread_t thread;
    pthread_create(&thread, NULL, threadFunction, &student);
    pthread_join(thread, NULL);


    return 0;
}

在这里插入图片描述

  • 新创建的线程从第三个参数(是一个函数地址)开始运行,该函数只有一个无类型的指针参数arg。如果想要传递一个以上的参数,需要将这些参数放到一个结构中,然后把这个结构地址作为arg参数传入

一个demo:

#include <iostream>
#include <pthread.h>
#include <cstring>
#include <unistd.h>

//*************新的错误检查与错误退出函数******************//
inline void err_exit(const std::string &msg, int retno){
    std::cerr << msg << ": " << std::strerror(retno) << std::endl;;
    exit(EXIT_FAILURE);
}
inline void err_check(const std::string &msg, int retno){
    if(retno != 0){
        err_exit(msg, retno);
    }
}

void *thread_rotine(void *args){
    for (int i = 0; i < 10; ++i)
    {
        printf("B");
        fflush(stdout);
        usleep(20);
    }
    pthread_exit(NULL);

}
int main() {
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, thread_rotine, NULL);
    err_check("pthread_create", ret);

    for (int i = 0; i < 10; ++i)
    {
        printf("A");
        fflush(stdout);
        usleep(20);
    }

    ret = pthread_join(thread, NULL);
    err_check("pthread_join", ret);

    return 0;
}

返回值:
每次的返回结果都不一样:

ABAABABABABABABABABB
ABBABAABBAABBABAABAB
ABABABABABABABABABAB
ABBBBABBBBBBAAAAAAAA
*****

demo

#include <iostream>
#include <pthread.h>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;
/** 将并发echo server改造成多线程形式 
注意线程竞速问题的解决
**/
inline void err_exit(const std::string &msg, int retno){
    std::cerr << msg << ": " << std::strerror(retno) << std::endl;;
    exit(EXIT_FAILURE);
}

inline void err_exit(const std::string &msg){
    std::cerr << msg << std::endl;;
    exit(EXIT_FAILURE);
}

void echo_server(int clientSocket);
void *thread_routine(void *arg);
int main() {
    // 创建 socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    /*
     * AF_INEF表示TCP/IP族
     * SOCK_STREAM:流套接字,对应TCP协议
     * 第三个参数为0,由操作系统自行选择
     * */
    if (sockfd == -1)
        err_exit("socket error");

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8002);
    serverAddr.sin_addr.s_addr = INADDR_ANY;    //绑定本机的任意一个IP地址
    if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1)
        err_exit("bind error");

    /*
     * 对于一个TCP连接,Server与Client需要通过三次握手来建立网络连接.当三次握手成功后,
     * 我们可以看到端口的状态由LISTEN转变为ESTABLISHED,接着这条链路上就可以开始传送数据了.
     */
    if (listen(sockfd,SOMAXCONN) == -1)
        err_exit("listen error");

    while (true){
        int peerSockfd = accept(sockfd, NULL, NULL);
        if (peerSockfd == -1)
            err_exit("accept error");

        pthread_t tid;

        /**注意: 下面这种用法可能会产生"竞速问题"
            当另一个连接快读快速到达, peerSockfd的内容更改,
            新创建的线程尚未将该值取走时,线程读取的就不是
            我们想让线程读取的值了
    int ret = pthread_create(&tid, NULL, thread_routine, (void *)&peerSockfd);
    **/
        //解决方案: 为每一个链接创建一块内存
        int *p = new int(peerSockfd);
        int ret = pthread_create(&tid, NULL, thread_routine, p);
        if (ret != 0)
            err_exit("pthread_create error", ret);
        
    }
    
    close(sockfd);
    return 0;
}

void *thread_routine(void *args)
{
    //将线程设置分离状态, 避免出现僵尸线程
    pthread_detach(pthread_self());
    int peerSockfd = *(int *)args;
    //将值取到之后就将这块内存释放掉
    delete (int *)args;

    echo_server(peerSockfd);
    cout << "thread " << pthread_self() << " exiting ..." << endl;
    pthread_exit(NULL);
}
void echo_server(int clientSocket){
    char buf[BUFSIZ] = {0};
    int readBytes;

    while ((readBytes = read(clientSocket, buf, sizeof(buf))) >= 0){
        if (readBytes == 0)
        {
            cerr << "client connect closed" << endl;
            break;
        }
        if (write(clientSocket, buf, readBytes) == -1)
        {
            cerr << "server thread write error" << endl;
            break;
        }
        cout << buf;
        bzero(buf, sizeof(buf));
    }
}

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

static pthread_spinlock_t __spin_lock;

static void *thread_nested_mutex(void *arg){
    time_t begin, end;
    int   i, n = 100000000;
    printf("tid: %lu, begin spin lock\n", (unsigned long int) pthread_self());

    time(&begin);
    for (i = 0; i < n; i++) {
        pthread_spin_lock(&__spin_lock);
        pthread_spin_unlock(&__spin_lock);
    }
    time(&end);
    printf("tid: %lu, lock over, spin, time: %ld\n",
           (unsigned long int) pthread_self(), (long) end - begin);
    return (NULL);
}
static void test_nested_mutex(void){
    pthread_t tid;
    pthread_attr_t attr;
    static pthread_mutex_t mutex;

    pthread_spin_init(&__spin_lock, 0);

    pthread_mutex_init(&mutex, NULL);
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_create(&tid, &attr, thread_nested_mutex, &mutex);
    pthread_create(&tid, &attr, thread_nested_mutex, &mutex);
}
int main(void)
{

    test_nested_mutex();
    while (1)
        sleep(10);
    return 0;
}


在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值