长治做网站的公司,西安营销型网站制作价格,手机怎么自己设计图片,dedecms 倒计时 天数 网站首页目录 线程与进程区别pthread库接口介绍pthread_createpthread_self和syscall(SYS_gettid);pthread_equal测试主线程的栈空间大概是多大pthread_setname_nppthread_exitpthread_join为什么要连接退出的线程 pthread_detach 线程与进程区别
进程是一个动态的实体#xff0c;有自… 目录 线程与进程区别pthread库接口介绍pthread_createpthread_self和syscall(SYS_gettid);pthread_equal测试主线程的栈空间大概是多大pthread_setname_nppthread_exitpthread_join为什么要连接退出的线程 pthread_detach 线程与进程区别
进程是一个动态的实体有自己的生命周期。线程是操作系统进程调度器可以调度的最小执行单位。
一个进程可能包含多个线程。
进程之间彼此的地址空间是独立的但同一个进程的多个线程共享内存地址空间即全局内存区域包括初始化数据段、未初始化数据段和动态分配的内存段。
这种共享给线程带来了更多的优势
创建线程花费的时间要比创建进程花费的时间少终止线程花费的时间要比终止进程花费的时间少线程之间上下文切换的开销要比进程之间上下文切换的开销小线程之间共享数据要比进程之间共享数据简单
线程共享地址空间的设计让多个线程之间的通信变得非常简单。一个进程内的多个线程就像一个软件研发小组内部的不同员工共享代码、服务器、打印机、资料彼此之间有分工协作沟通协作成本比较低。进程之间的通信代价则要高很多。进程之间不得不采用一些进程间通信的手段如管道、共享内存及信号量等来协作。
需要强调的一点是线程和进程不一样进程有父进程的概念但在线程组里所有的线程都是对等的关系
并不是只有主线程才能创建线程被创建出来的线程同样可以创建线程。不存在类似于fork函数那样的父子关系大家都归属于同一个线程组进程ID都相等而且各有各的线程ID。并非只有主线程才能调用pthread_join连接其他线程同一线程组内的任意线程都可以对某线程执行pthread_join函数。并非只有主线程才能调用pthread_detach函数其实任意线程都可以对同一线程组内的线程执行分离操作。
pthread库接口介绍
POSIX函数函数功能描述pthread_create创建一个线程pthread_exit退出线程pthread_self获取线程IDpthread_equal检查两个线程ID是否相等pthread_join等待线程退出pthread_detach设置线程状态为分离状态pthread_cancel线程的取消pthread_setname_np设置线程的名称pthread_cleanup_push线程退出清理函数注册和执行pthread_cleanup_pop线程退出清理函数注册和执行
pthread_create
程序开始启动的时候产生的进程只有一个线程我们称之为主线程或初始线程。对于单线程的进程而言只存在主线程一个线程。如果想在主线程之外再创建一个或多个线程就需要用到这个接口了。
函数接口如下
#include pthread.h
int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void*),void *restrict arg);
pthread_create函数的第一个参数是pthread_t类型的指针线程创建成功的话会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。第二个参数是pthread_attr_t类型通过该参数可以定制线程的属性比如可以指定新建线程栈的大小、调度策略等。如果创建线程无特殊的要求该值也可以是NULL表示采用默认属性。第三个参数是线程需要执行的函数。创建线程是为了让线程执行一定的任务。线程创建成功之后该线程就会执行start_routine函数该函数之于线程就如同main函数之于主线程。第四个参数是新建线程执行的start_routine函数的入参。
返回值如果成功则pthread_create返回0如果不成功则pthread_create返回一个非0的错误码。常见的错误码如表
返回值描述EAGAIN系统资源不够或者创建线程的个数超过系统对一个进程中线程总数的限制EINVAL第二个参数attr不合法EPERM没有合适的权限来设置调度策略或参数
// pthread_create示例#include iostream
#include pthread.h
#include errno.h
#include unistd.hvoid* ThreadFunc(void* _threadId)
{std::cout 线程ID: pthread_self() hello world std::endl;pthread_exit(nullptr);
}int main()
{// 创建五个线程pthread_t tid[5];for (int i 0;i 5;i){int rc pthread_create(tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc ! 0){std::cout 线程创建失败 std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}pthread_self和syscall(SYS_gettid);
这两个函数都是获取自身的线程ID #include pthread.hpthread_t pthread_self();// --------------------------------- //int TID syscall(SYS_gettid);区别是
syscall(SYS_gettid)属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一标识该线程。pthread_self()属于NPTL线程库的范畴线程库的后续操作就是根据该线程ID来操作线程的。
// 示例#include iostream
#include pthread.h
#include errno.h
#include string.h
#include unistd.h
#include sys/syscall.hvoid* ThreadFunc(void* _threadId)
{int TID syscall(SYS_gettid);std::cout TID: TID std::endl;std::cout pthread_self: pthread_self() std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;int rc pthread_create(tid, nullptr, ThreadFunc, (void*)tid);if (rc ! 0){std::cout 线程创建失败 std::endl;;return -1;}// 等待线程退出int res pthread_join(tid, nullptr);if (res ! 0){std::cout strerror(res) std::endl;return -1;}std::cout 线程已经退出 std::endl;return 0;
}[rootZhn 线程]# g pthread_detach.cpp -o pthread_detach -lpthread
[rootZhn 线程]# ./pthread_detach
TID: 8718
pthread_self: 140005981333248
线程已经退出
[rootZhn 线程]# pthread_equal
在同一个线程组内线程库提供了接口可以判断两个线程ID是否对应着同一个线程
#include pthread.hint pthread_equal(pthread_t t1, pthread_t t2);返回值是0的时候表示两个线程是同一个线程非零值则表示不是同一个线程。
pthread_t类型的线程ID本质就是一个进程地址空间上的一个地址。
测试主线程的栈空间大概是多大
// 查看一个线程的栈最大占用多少内存空间#include iostream
#include pthread.h
#include unistd.hint i 0;void func()
{// int类型是4个字节256个int类型是4 * 256 1024也就是1kB// 每执行一次func函数就会占用1kB的栈空间看看可以执行多少次这个函数int buffer[256];std::cout i i std::endl;i;func();
}int main()
{func();sleep(100);
}可以执行8053次每次是1kB也就是8053 / 1024大概是8MB。
pthread_setname_np
给线程设置名称
#include pthread.hint pthread_setname_np(pthread_t thread, const char *name);// pthread_setname_np示例#include iostream
#include pthread.h
#include errno.h
#include unistd.hvoid* ThreadFunc(void* _threadId)
{// 设置线程名称pthread_setname_np(pthread_self(), std::to_string(pthread_self()).c_str());while (1){std::cout 线程ID: pthread_self() hello world std::endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid[5];for (int i 0;i 5;i){int rc pthread_create(tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc ! 0){std::cout 线程创建失败 std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}创建了五个线程给每个线程设置名称为自己线程ID。 CMD字段就是设置的线程名称。
pthread_exit
有生就有灭线程执行完任务也需要终止。
下面的三种方法中线程会终止但是进程不会终止如果线程不是进程组里的最后一个线程的话
创建线程时的start_routine线程执行函数函数执行了return并且返回指定值。线程调用pthread_exit。其他线程调用了pthread_cancel函数取消了该线程。如果线程组中的任何一个线程调用了exit函数或者主线程在main函数中执行了return语句那么整个线程组内的所有线程都会终止。
pthread_exit函数的定义
#include pthread.hvoid pthread_exit(void *value_ptr);value_ptr是一个指针存放线程的“临终遗言”。线程组内的其他线程可以通过调用pthread_join函数接收这个地址从而获取到退出线程的临终遗言。如果线程退出时没有什么遗言则可以直接传递NULL指针如下所示
pthread_exit(NULL);但是这里有一个问题就是不能将遗言存放到线程的局部变量里因为如果用户写的线程函数退出了线程函数栈上的局部变量可能就不复存在了线程的临终遗言也就无法被接收者读到。
那我们应该如何正确地传递返回值呢
如果是int型的变量则可以使用“pthread_exitint*ret”。使用全局变量返回。将返回值填入到用malloc在堆上分配的空间里。·使用字符串常量如pthread_exit“helloworld”。
线程退出有一种比较有意思的场景即线程组的其他线程仍在执行的情况下主线程却调用pthread_exit函数退出了。这会发生什么事情首先要说明的是这不是常规的做法但是如果真的这样做了那么主线程将进入僵尸状态而其他线程则不受影响会继续执行。
pthread_join
线程库提供了pthread_join函数用来等待某线程的退出并接收它的返回值。这种操作被称为连接joining。
#include pthread.hint pthread_join(pthread_t thread, void **retval);第一个参数表示要等待的线程ID第二个参数代表线程退出的返回值。
根据等待的线程是否退出可得到如下两种情况
等待的线程尚未退出那么pthread_join的调用线程就会陷入阻塞。等待的线程已经退出那么pthread_join函数会将线程的退出值void*类型存放到retval指针指向的位置。
线程的连接join操作有点类似于进程等待子进程退出的等待wait操作但还是有不同之处
第一点不同之处是进程之间的等待只能是父进程等待子进程而线程则不然。线程组内的成员是对等的关系只要是在一个线程组内就可以对另外一个线程执行连接join操作。第一点不同之处是进程之间的等待只能是父进程等待子进程而线程则不然。线程组内的成员是对等的关系只要是在一个线程组内就可以对另外一个线程执行连接join操作。
返回值
返回值说明ESRCH传入的线程ID不存在查无此线程EINVAL线程不是一个可连接的线程或者已经有其他线程连接EDEADLK死锁
pthread_join不能连接线程组内任意线程的做法并不是NPTL线程库设计上的瑕疵而是有意为之的。如果听任线程连接线程组内的任意线程那么所谓的任意线程就会包括其他库函数私自创建的线程当库函数尝试连接join私自创建的线程时发现已经被连接过了就会返回EINVAL错误。如果库函数需要根据返回值来确定接下来的流程这就会引发严重的问题。正确的做法是连接已知线程ID的那些线程就像pthread_join函数那样。
pthread_join函数之所以能够判断是否死锁和连接操作是否被其他线程捷足先登是因为目标线程的控制结构体struct pthread中存在如下成员变量记录了该线程的连接者
struct pthread *joinid该指针存在三种可能如下。
NULL线程是可连接的但是尚没有其他线程调用pthread_join来连接它。指向线程自身的struct pthread表示该线程属于自我了断型执行过分离操作或者创建线程时设置的分离属性为PTHREAD_CREATE_DETACHED一旦退出则自动释放所有资源无需其他线程来连接。指向线程组内其他线程的struct pthread表示joinid对应的线程会负责连接。
为什么要连接退出的线程
如果不连接退出的线程会导致资源无法释放
如果不执行连接操作线程的资源就不能被释放也不能被复用这就造成了资源的泄漏。
值得一提的是纵然调用了pthread_join也并没有立即调用munmap来释放掉退出线程的栈它们是被后建的线程复用了这是NPTL线程库的设计。释放线程资源的时候NPTL认为进程可能再次创建线程而频繁地munmap和mmap会影响性能所以NTPL将该栈缓存起来放到一个链表之中如果有新的创建线程的请求NPTL会首先在栈缓存链表中寻找空间合适的栈有的话直接将该栈分配给新创建的线程。
始终不将线程栈归还给系统也不合适所有缓存的栈大小有上限默认是40MB如果缓存起来的线程栈的空间总和大于40MBNPTL就会扫描链表中的线程栈调用munmap将一部分空间归还给系统。
// pthread_join示例#include iostream
#include pthread.h
#include unistd.h#define Threads 5void* ThreadFunc(void* _args)
{int thread_num *((int*)_args);printf(Thread %d: Hello, World!\n, thread_num);int* retval new int;*retval thread_num * 2;pthread_exit(reinterpret_castvoid*(retval));
}int main()
{pthread_t tid[Threads];for (int i 0;i Threads;i){int* p new int;*p i;int rc pthread_create(tid[i], nullptr, ThreadFunc, reinterpret_castvoid*(p));if (rc ! 0){std::cout 线程 i 创建失败 std::endl;return -1;}}// 等待线程结束并接收返回值for (int i 0;i Threads; i){void* retval;pthread_join(tid[i], retval);std::cout 线程 i 退出, 返回值是 *reinterpret_castint*(retval) std::endl;}std::cout 线程全部退出 std::endl;return 0;
}[rootZhn 线程]# g pthread_join.cpp -o pthread_join -lpthread
[rootZhn 线程]# ./pthread_join
Thread 0: Hello, World!
Thread 1: Hello, World!
Thread 3: Hello, World!
Thread 2: Hello, World!
Thread 4: Hello, World!
线程0退出, 返回值是0
线程1退出, 返回值是2
线程2退出, 返回值是4
线程3退出, 返回值是6
线程4退出, 返回值是8
线程全部退出
[rootZhn 线程]# 可以看到程序会阻塞在pthread_join直到所有线程全部退出。
pthread_detach
默认情况下新创建的线程处于可连接Joinable的状态可连接状态的线程退出后需要对其执行连接操作否则线程资源无法释放从而造成资源泄漏。
如果其他线程并不关心线程的返回值那么连接操作就会变成一种负担你不需要它但是你不去执行连接操作又会造成资源泄漏。这时候你需要的东西只是线程退出时系统自动将线程相关的资源释放掉无须等待连接。NPTL提供了pthread_detach函数来将线程设置成已分离detached的状态如果线程处于已分离的状态那么线程退出时系统将负责回收线程的资源如下
#include pthread.hint pthread_detach(pthread_t thread);参数就是要分离的线程ID可以是线程组内其他线程对目标线程进行分离也可以是线程自己执行pthread_detach函数将自身设置成已分离的状态如下
pthread_detach(pthread_self())线程的状态之中可连接状态和已分离状态是冲突的一个线程不能既是可连接的又是已分离的。因此如果线程处于已分离的状态其他线程尝试连接线程时会返回EINVAL错误。
返回值
返回值说明ESRCH传入的线程ID不存在查无此线程EINVAL线程不是一个可连接的线程已经处于分离状态
// pthread_detach示例#include iostream
#include pthread.h
#include errno.h
#include string.h
#include unistd.h
#include sys/syscall.hvoid* ThreadFunc(void* _threadId)
{int TID syscall(SYS_gettid);std::cout TID: TID std::endl;std::cout pthread_self: pthread_self() std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;#ifdef USE_ATTRpthread_attr_t attr;// 将线程状态设置为分离状态pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);int rc pthread_create(tid, nullptr, ThreadFunc, (void*)tid);if (rc ! 0){std::cout 线程创建失败 std::endl;;return -1;}#elseint rc pthread_create(tid, nullptr, ThreadFunc, (void*)tid);if (rc ! 0){std::cout 线程创建失败 std::endl;;return -1;}int res pthread_detach(tid);if (res ! 0){std::cout strerror(res) std::endl;return -1;}#endifstd::cout 已设置线程分离 std::endl;return 0;
}[rootZhn 线程]# g pthread_detach.cpp -o pthread_use_attr_detach -DUSE_ATTR -lpthread
[rootZhn 线程]# g pthread_detach.cpp -o pthread_no_attr_detach -lpthread
[rootZhn 线程]# ./pthread_no_attr_detach
已设置线程分离TID:
8428
pthread_self: pthread_self: 139697266493184 hello world
[rootZhn 线程]# ./pthread_no_attr_detach
已设置线程分离TID: 8485
pthread_self: 140716723963648 hello world[rootZhn 线程]# ./pthread_use_attr_detach
已设置线程分离
[rootZhn 线程]# 我们使用两种方式设置线程分离一种是通过属性设置还有一种是调用pthread_detach设置分离执行了三次程序可以发现第三次线程没有输出这是为什么因为我们设置线程分离那么主线程先抢到CPU执行之后就退出了还没轮到子线程执行。