Linux POSIX线程介绍
1、线程概述
与进程相似,线程是允许应用程序并发执行多个任务的机制。一个进程可以包含多个线程, 同一程序中的所有线程均共享同一份全局内存区域,其中包括:初始化数据段,未初始化数据段,以及堆内存断。
线程 Thread:专业术语称之为程序执行流的最小单元 。线程
独立执行程序中的某个任务。
2、线程和进程的关系
- 一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。
- 资源分配给进程,同一个进程的所有线程共享该进程所有资源。
- CPU分配给线程,即真正在处理器运行的是线程。
- 线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。
进程是资源分配的最小单位,线程是CPU调度的最小单位。
开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间切换会有较大的开销;线程是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换开销小。
在Linux中,轻量级进程可以是进程,也可以是线程。在Linux中,轻量级进程之间共享代码段,文件描述符,信号处理,全局变量时,该轻量级进程即为我们所说的线程。如果不共享,则为进程。
线程进程基本操作
功能 | 进程 | 线程 |
---|---|---|
创建 | fork() | pthread_create() |
退出 | exit() | pthread_exit() |
等待 | wait()/waitpid() | pthread_join() |
取消 | abort() | pthread_cancel() |
获取ID | getpid() | pthread_self() |
通信机制 | 管道、消息队列、共享内存、信号、信号量 | 信号、信号量、互斥锁、读写锁、条件变量 |
3、线程共享的属性
- 全局内存
- 进程ID 和父进程
- 进程组ID和会话ID
- 控制终端
- 打开的文件描述符(演示)
- 信号处理
- 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 | 线程的属性对象 |
- 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;
- pthread_mutex_t
typedef union
{
struct __pthread_mutex_s __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
- 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;
- 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;
- pthread_key_t
/* Keys for thread-specific data */
typedef unsigned int pthread_key_t;
- pthread_once_t
/* Once-only execution */
typedef int __ONCE_ALIGNMENT pthread_once_t;
- 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()返回相同值,