Linux 线程

Linux POSIX线程介绍

1、线程概述

与进程相似,线程是允许应用程序并发执行多个任务的机制。一个进程可以包含多个线程, 同一程序中的所有线程均共享同一份全局内存区域,其中包括:初始化数据段未初始化数据段,以及堆内存断

线程 Thread:专业术语称之为程序执行流的最小单元 。线程
独立执行程序中的某个任务。

2、线程和进程的关系

  1. 一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。
  2. 资源分配给进程,同一个进程的所有线程共享该进程所有资源。
  3. CPU分配给线程,即真正在处理器运行的是线程。
  4. 线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

进程是资源分配的最小单位,线程是CPU调度的最小单位。

开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间切换会有较大的开销;线程是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换开销小。

在Linux中,轻量级进程可以是进程,也可以是线程。在Linux中,轻量级进程之间共享代码段,文件描述符,信号处理,全局变量时,该轻量级进程即为我们所说的线程。如果不共享,则为进程。

线程进程基本操作

功能进程线程
创建fork()pthread_create()
退出exit()pthread_exit()
等待wait()/waitpid()pthread_join()
取消abort()pthread_cancel()
获取IDgetpid()pthread_self()
通信机制管道、消息队列、共享内存、信号、信号量信号、信号量、互斥锁、读写锁、条件变量

3、线程共享的属性

  1. 全局内存
  2. 进程ID 和父进程
  3. 进程组ID和会话ID
  4. 控制终端
  5. 打开的文件描述符(演示)
  6. 信号处理
  7. errno变量

4、线程相关变量

数据类型描述
pthread_t线程ID
pthread_mutex_t互斥对象(Mutex)
pthread_mutexattr_t互斥属性对象
pthread_cond_t条件变量(conditionvariable)
pthread_condattr_t条件变量的属性对象
pthread_key_t线程特有数据的健(key)
pthread_once_t一次性初始化控制上下文(control context)
pthread_attr_t线程的属性对象
  1. pthread_t: 线程ID
    在使用线程ID时需要注意类型,在不同平台中定义不同。
//Linux
typedef unsigned long int pthread_t;

//Windows
typedef struct 
{  
	void * p;                   /* Pointer to actual object */  
	unsigned int x;             /* Extra information - reuse count etc */  
} ptw32_handle_t;  
typedef ptw32_handle_t pthread_t;  
  1. pthread_mutex_t
typedef union
{
  struct __pthread_mutex_s __data;
  char __size[__SIZEOF_PTHREAD_MUTEX_T];
  long int __align;
} pthread_mutex_t;

  1. pthread_mutexattr_t
/* Data structures for mutex handling.  The structure of the attribute
   type is not exposed on purpose.  */
typedef union
{
  char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
  int __align;
} pthread_mutexattr_t;

  1. pthread_condattr_t
/* Data structure for condition variable handling.  The structure of
   the attribute type is not exposed on purpose.  */
typedef union
{
  char __size[__SIZEOF_PTHREAD_CONDATTR_T];
  int __align;
} pthread_condattr_t;

  1. pthread_key_t
/* Keys for thread-specific data */
typedef unsigned int pthread_key_t;
  1. pthread_once_t
/* Once-only execution */
typedef int __ONCE_ALIGNMENT pthread_once_t;
  1. pthread_attr_t
union pthread_attr_t
{
  char __size[__SIZEOF_PTHREAD_ATTR_T];
  long int __align;
};
typedef union pthread_attr_t pthread_attr_t;

4、线程API

创建线程 pthread_create

/* Create a new thread, starting with execution of START-ROUTINE
   getting passed ARG.  Creation attributed come from ATTR.  The new
   handle is stored in *NEWTHREAD.  */
extern int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void *), void *arg);
入参含义
pthread_t *thread指向线程标识符的指针,也就是线程对象的指针
const pthread_attr_t *attr线程属性
void *(*start)(void *)线程运行函数的地址 ,参数是void *,返回值也是void *;
void *arg运行函数的参数
pthread_t thread_ID;
int ret;
void thread_func();

ret = pthread_create(&thread_ID, NULL, thread_func, NULL);

三个因素影响最大线程数:

/proc/sys/kernel/threads-max,表示系统支持的最大线程数,默认值是 14553/proc/sys/kernel/pid_max,表示系统全局的 PID 号数值的限制,每一个进程或线程都有 ID,ID 的值超过这个数,进程或线程就会创建失败,默认值是 32768/proc/sys/vm/max_map_count,表示限制一个进程可以拥有的VMA(虚拟内存区域)的数量,具体什么意思我也没搞清楚,反正如果它的值很小,也会导致创建线程失败,默认值是 65530

通过/proc/sys/kernel/threads-max 查看可以创建多少线程

max@ubuntu:~$ cat /proc/sys/kernel/threads-max
30617

线程等待,回收相关内存 pthread_join()

功能:阻塞调用线程,直到指定的线程终止。默认情况下,线程是可连接的(joinable),也就是说,当线程退出时,其他线程可以通过调用该方法来获取其返回状态。

int pthread_join(pthread_t thread, void **retval);
入参含义
pthread_t thread线程ID
void **retval线程终止时返回值的拷贝,即线程调用return或thread_exit()时所指定的值。
int ret;
void * thread_result;
pthread_t thread_ID;
void thread_func();
//创建 myThread 线程
ret = pthread_create(&thread_ID, NULL, thread_func, NULL);
if (ret != 0) {
    printf("线程创建失败");
    return 0;
}
//阻塞主线程,等待 myThread 线程执行结束
ret = pthread_join(thread_ID, &thread_result);
printf("thread:[%u] return:[%s]\n, thread_ID, thread_result);

线程终止 pthread_exit

线程终止有以下方式:

  • 线程执行函数执行return语句并返回指定值。
  • 线程调用pthread_exit().
  • 调用pthread_cancel()取消线程。
  • 任意线程调用exit(), 或主线程main函数中执行return语句,进程结束,所有线程终止。

pthread_exit()与在线程执行函数中调用return大致基本相同,不同之处在于,pthread_exit()可以在执行函数中的任意函数中执行,return 只能在执行函数本身中调用才能终止线程。

void pthread_exit(void *retval);
retval表示线程退出状态,通常传NULL

如果在主线程中使用pthread_exit函数,会导致子线程还在,内存无法被回收,成为僵尸进程。

线程分离 pthread_detach

作用:从状态上实现线程分离,注意不是指该线程独自占用地址空间
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。
进程没有分离机制。

默认情况下,线程启动后处于可接合状态(即未分离), 一般情况下,线程终止后,其终止状态会一直被保留到其他线程调用pthread_join获取它的状态为止或进程终止。当线程被设置为detech状态时,线程一旦终止,它就会立刻回收它占有的所有资源,并且不保留其终止状态。所以不能对一个detech状态的线程使用pthread_join,调用pthread_join时会返回失败。
当线程退出时不需要汇报返回值时,一般将线程设置为分离状态,分离线程在线程退出后自动释放占用资源。

线程分离方法:
一、在创建线程前,使用分离属性启动线程。
二、使用pthread_detach 函数实现线程分离。

int pthread_detach (pthread_t thread);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

void *thread_func(void *arg)
{
    int n = 3;

    while (n--)
    {
        printf("thread count %d\n", n);
        sleep(1);
    }

    // return (void *)1;
    pthread_exit((void *)1);
}

int main(void)
{
    pthread_t tid;
    void *pthread_result;
    int err;

#if 1
 
        pthread_attr_t attr;            /*通过线程属性来设置游离态(分离态)*/
        pthread_attr_init(&attr);
        // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        pthread_create(&tid, &attr, thread_func, NULL);

#else

    pthread_create(&tid, NULL, thread_func, NULL);
    pthread_detach(tid); //让线程分离  ----自动退出,无系统残留资源

#endif

    while (1)
    {
        err = pthread_join(tid, &pthread_result);
        printf("-----err = [%d]--------------\n", err);
        if (err != 0)
            printf("thread_join error: %s\n", strerror(err));
        else
            printf("thread exit code %d\n", (int)pthread_result);

        sleep(1);
    }

    return 0;
}

线程属性

线程属性主要包含分离状态,作用域,栈尺寸,栈地址,优先级,调度策略和参数。
在创建线程时,第二个参数pthread_attr_t 的attr参数,指定创建线程时指定新线程的属性。

max@ubuntu:~$ man pthread_attr_
pthread_attr_destroy          pthread_attr_getsigmask_np    pthread_attr_setinheritsched
pthread_attr_getaffinity_np   pthread_attr_getstack         pthread_attr_setschedparam
pthread_attr_getdetachstate   pthread_attr_getstackaddr     pthread_attr_setschedpolicy
pthread_attr_getguardsize     pthread_attr_getstacksize     pthread_attr_setscope
pthread_attr_getinheritsched  pthread_attr_init             pthread_attr_setsigmask_np
pthread_attr_getschedparam    pthread_attr_setaffinity_np   pthread_attr_setstack
pthread_attr_getschedpolicy   pthread_attr_setdetachstate   pthread_attr_setstackaddr
pthread_attr_getscope         pthread_attr_setguardsize     pthread_attr_setstacksize

相关函数API:

API作用
pthread_attr_init()初始化属性结构体
pthread_attr_destory()销毁线程属性结构体
pthread_getattr_np()获取线程属性结构体
pthread_attr_getdetachstate()设置分离属性
pthread_attr_getguardsize( )获取栈警戒区大小
pthread_attr_getinheritsched( )获取继承策略
pthread_attr_getschedpolicy( )获取调度策略
pthread_attr_setschedpolicy( )设置调度策略
pthread_attr_getschedparam( )获取调度参数
pthread_attr_getscope( )获取竞争范围
pthread_attr_setscope( )设置竞争范围
pthread_attr_getaffinity_np( )获取CPU亲和度
pthread_attr_setaffinity_np( )设置CPU亲和度
pthread_attr_getstack( )获取栈指针和栈大小
pthread_attr_setstack( )设置栈的位置和栈大小
pthread_attr_getstacksize( )获取栈大小
pthread_attr_setstacksize( )设置栈大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

void *thread_func(void *arg)
{
    int n = 3;

    while (n--)
    {
        printf("thread count %d\n", n);
        sleep(1);
    }

    // return (void *)1;
    pthread_exit((void *)1);
}

int main(void)
{
    pthread_t tid;
    void *pthread_result;
    int err;

    pthread_attr_t attr; /*通过线程属性来设置游离态(分离态)*/
    size_t size;
    void *stackaddr;
    pthread_attr_init(&attr);
    // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_attr_getguardsize(&attr, &size);
    printf("guardsize:%ld\n", size);

    size = 0;
    pthread_attr_getstacksize(&attr, &size);
    printf("stacksize:%ld\n", size);

    pthread_attr_setstacksize(&attr, 90000);
    size = 0;
    pthread_attr_getstacksize(&attr, &size);
    printf("stacksize:%ld\n", size);

    pthread_create(&tid, &attr, thread_func, NULL);

    while (1)
    {
        err = pthread_join(tid, &pthread_result);
        printf("-----err = [%d]--------------\n", err);
        if (err != 0)
            printf("thread_join error: %s\n", strerror(err));
        else
            printf("thread exit code %d\n", (int)pthread_result);

        sleep(1);
    }

    return 0;
}

clone()函数

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

创建线程的函数pthread_create内部使用的也是clone函数。

vfork() 
CLONE_VM | CLONE_VFORK | SIGCHLD

Linux Thread
CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#define _SCHED_H 1
#define __USE_GNU 1
#include <bits/sched.h>
 
#define STACK_SIZE 4096
 
int func(void *arg) {
    printf("Inside func.\n");
    while(1);
    printf("Terminating func...\n");
 
    return 0;
}
 
int main() {
    printf("This process pid: %u\n",getpid());
    char status_file[] = "/proc/self/status";
    void *child_stack = malloc(STACK_SIZE);
    int thread_pid;
 
    printf("Creating new thread...\n");
    thread_pid =  (&func,child_stack+STACK_SIZE,CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES|CLONE_THREAD,NULL);
    printf("Done! Thread pid: %d\n",thread_pid);

    while(1);
    return 0;
}
max@ubuntu:~$ ps -eLf | grep thread
root           2       0       2  0    1 05:01 ?        00:00:00 [kthreadd]
max        10594   10584   10594 99    2 06:33 ?        00:00:11 /home/max/C/thread/clone
max        10594   10584   10599 99    2 06:33 ?        00:00:11 /home/max/C/thread/clone
Flag作用
CLONE_VM共享父进程虚拟内存
CLONE_FILES共享文件描述符表
CLONE_FS共享与文件系统相关的信息
CLONE_SIGHAND共享对信号的处置设置
CLONE_THREAD将子进程添加进父进程的线程组中

CLONE_THREAD:进程的所有线程共享同一进程ID, 使用getpid()返回相同值,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值