线程互斥与同步
- 👸 理解线程
- 🤴pthead_t
- 🥷关于线程
- 🦸♀️线程控制
- POSIX线程库
- 线程ID及进程地址空间布局
- 🦸线程分离
- __thread关键字
- 🦸♂️pthread_detach函数
- 🦹♀️pthread_exit函数
- 🦹 exit和pthread_exit
👸 理解线程
🤴pthead_t
pthread_t 是 POSIX 线程库中的数据类型,用于表示线程标识符。POSIX(Portable Operating System Interface for Unix)是一套标准,定义了在 UNIX 系统中的应用程序编程接口(API)规范,其中包含了线程操作的标准接口。
在多线程编程中,每个线程都有一个唯一的标识符,用于区分不同的线程。pthread_t 就是用来存储这个线程标识符的数据类型。它在 <pthread.h> 头文件中定义。
在使用 POSIX 线程库创建线程时,会得到一个 pthread_t 类型的变量,用于标识新创建的线程。您可以使用 pthread_t 变量来操作、控制或等待特定的线程。通常情况下,我们通过调用 pthread_create 函数来创建新线程,并将新线程的标识符保存在 pthread_t 变量中。
🥷关于线程
关于线程我们要知道以下概念
- 线程是一个独立的执行流
- 线程一定会在自己的执行过程中产生临时数据(调用函数,定义局部变量等等)
- 线程一定有自己的独立栈结构
🦸♀️线程控制
POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
- 创建
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(start_routine)(void), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
- 错误检查:
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通
过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,
建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
线程ID及进程地址空间布局
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要
一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,
属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
🦸线程分离
- 代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include<iostream>
using namespace std;
int N=1000;
void* threadF1(void *argv)
{
while (true)
{
cout<<"thread:"<<pthread_self()<<" "<<"N的值:"<<N<<" "<<"&N:"<<" "<<&N<<"Inc:"<<N++<<endl;
sleep(1);
}
}
int main()
{
pthread_t td1;
pthread_t td2;
pthread_t td3;
pthread_create(&td1,nullptr,threadF1,(void*)"td1");
pthread_create(&td2,nullptr,threadF1,(void*)"td2");
pthread_create(&td3,nullptr,threadF1,(void*)"td3");
pthread_join(td1,nullptr);
pthread_join(td2,nullptr);
pthread_join(td3,nullptr);
return 0;
}
- 结果
总结起来就是 共享的资源N 在进行++操作的时候 多个线程访问的是同一个N 那么怎么让线程分离呢?
__thread关键字
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include<iostream>
#include <sys/syscall.h>
using namespace std;
__thread int global_value=1000;
void* threadF1(void *argv)
{
while (true)
{
cout << "thread " << pthread_self() << " global_value: "
<< global_value << " &global_value: " << &global_value
<< " Inc: " << global_value++ << " lwp: " << ::syscall(SYS_gettid)<<endl;
sleep(1);
// mak break;
}
}
int main()
{
pthread_t td1;
pthread_t td2;
pthread_t td3;
pthread_create(&td1,nullptr,threadF1,(void*)"td1");
pthread_create(&td2,nullptr,threadF1,(void*)"td2");
pthread_create(&td3,nullptr,threadF1,(void*)"td3");
pthread_join(td1,nullptr);
pthread_join(td2,nullptr);
pthread_join(td3,nullptr);
return 0;
}
看把共享数据块global_value用 __thread修饰即可 !
- 结果:
🦸♂️pthread_detach函数
pthread_detach 函数用于将一个线程标记为“可分离”的状态。当一个线程被标记为“可分离”,则在该线程终止时,线程的资源会自动释放,而无需其他线程调用 pthread_join 来等待和回收该线程的资源。
具体来说,pthread_detach 函数用于向线程库指示,当目标线程(被调用 pthread_detach 的线程)终止时,其状态和资源可以被系统回收,而不需要其他线程调用 pthread_join 来等待其终止。
使用 pthread_detach 的好处是,它可以防止资源泄漏。如果不使用 pthread_detach,当一个线程终止时,它的资源将一直保留在系统中,直到其他线程调用 pthread_join 来回收它的资源。如果没有及时回收资源,可能会导致资源泄漏。
在实际应用中,通常将不需要回收线程资源的线程标记为“可分离”,而将需要回收资源的线程标记为“非分离”。标记线程为“非分离”后,其他线程可以使用 pthread_join 来等待它的终止,并回收其资源。
要使用 pthread_detach,只需在目标线程中调用它即可,例如
#include <pthread.h>
#include <stdio.h>
void* threadFunction(void* arg) {
// Thread logic
printf("Thread function executed.\n");
return NULL;
}
int main() {
pthread_t thread;
if (pthread_create(&thread, NULL, threadFunction, NULL) != 0) {
perror("pthread_create");
return 1;
}
// Mark the thread as detachable
if (pthread_detach(thread) != 0) {
perror("pthread_detach");
return 1;
}
// Continue with other tasks, no need to call pthread_join.
// Sleep to give the thread time to execute before the program exits
sleep(1);
return 0;
}
在上述示例中,pthread_detach 函数被调用,将线程 thread 标记为“可分离”的状态。因此,我们不需要在主线程中调用 pthread_join 来回收线程资源。在主线程中可以继续进行其他任务,线程 thread 的资源会在它终止时自动释放
🦹♀️pthread_exit函数
pthread_exit 函数用于在线程内部终止当前线程的执行,并返回一个指定的退出值。这个函数允许线程在任何地方终止,而不必等待线程的函数返回。
函数原型:
#include <pthread.h>
void pthread_exit(void *retval);
参数 retval 是一个指向任意类型的指针,它表示线程的退出值。这个值可以被其他线程通过 pthread_join 函数获取到。
当一个线程调用 pthread_exit 函数时,它会立即终止当前线程的执行,并将 retval 指向的值传递给等待它的线程。
pthread_exit 的使用场景包括:
在线程执行完任务后,通过 pthread_exit 终止线程,而不是返回到线程的创建点。
在线程内部发现错误或条件,需要立即终止线程的执行。
在线程执行过程中遇到某种情况需要立即退出,并向其他线程传递一些信息。
以下是一个简单的示例,演示了 pthread_exit 的使用:
#include <pthread.h>
#include <stdio.h>
void* threadFunction(void* arg) {
// Thread logic
printf("Thread function executed.\n");
int exitValue = 42;
pthread_exit((void*) &exitValue);
}
int main() {
pthread_t thread;
if (pthread_create(&thread, NULL, threadFunction, NULL) != 0) {
perror("pthread_create");
return 1;
}
void* returnValue;
if (pthread_join(thread, &returnValue) != 0) {
perror("pthread_join");
return 1;
}
// Cast the return value back to its original type
int exitValue = *(int*) returnValue;
printf("Thread exit value: %d\n", exitValue);
return 0;
}
在上述示例中,线程函数 threadFunction 调用 pthread_exit 来终止线程的执行,并传递了一个整数值作为退出值。主线程通过 pthread_join 来等待线程的结束,并获取线程的退出值,然后打印出来
🦹 exit和pthread_exit
pthread_exit 和 exit 都可以用于终止程序的执行,但它们之间有几个关键的区别:
作用范围:
pthread_exit 仅用于终止调用它的线程的执行,不会终止整个进程。
exit 会立即终止整个进程的执行,包括所有线程。
参数传递:
pthread_exit 允许在线程内部传递一个指向任意类型的指针作为退出值,其他线程可以通过 pthread_join 来获取这个退出值。
exit 的退出值必须是整数类型,它会作为进程的退出状态传递给操作系统。
资源回收:
pthread_exit 不会自动释放线程占用的资源,因此需要在适当的地方手动释放资源。
exit 会自动释放整个进程占用的资源,包括打开的文件、动态分配的内存等。
使用场景:
pthread_exit 通常用于线程内部,在线程完成任务后主动退出,或者在线程内部遇到错误时终止线程的执行。
exit 通常用于整个程序,在程序完成主要任务后终止整个进程的执行,一般在 main 函数的末尾或者需要提前退出程序的地方使用。
注意:在多线程程序中,使用 exit 可能会导致一些问题,因为它会立即终止整个进程,可能会导致其他线程的资源无法正确释放,从而造成资源泄漏或未定义行为。在多线程程序中,推荐使用 pthread_exit 来终止线程的执行,以保证资源的正确释放。
🦹♂️ 🤶 🧑🎄 🎅 🧙♀️ 🧙 🧙♂️ 🧝♀️ 🧝 🧝♂️ 🧛♀️ 🧛 🧛♂️ 🧟♀️ 🧟 🧟♂️ 🧞♀️ 🧞 🧞♂️ 🧜♀️ 🧜 🧜♂️ 🧚♀️ 🧚 🧚♂️ 👼