像优酷这样的网站需要怎么做,建立网站后怎样收费,百度 seo排名查询,知乎推广优化线程的概念
基本概念
所谓线程#xff0c;通俗的说就是一个正在运行的函数。
在Linux系统中#xff0c;线程是程序运行的最小单位#xff0c;也被视为进程内部的控制序列。同一进程下的多个线程共享进程的所有资源#xff0c;包括进程环境变量、打开的文件描述符、信号量…线程的概念
基本概念
所谓线程通俗的说就是一个正在运行的函数。
在Linux系统中线程是程序运行的最小单位也被视为进程内部的控制序列。同一进程下的多个线程共享进程的所有资源包括进程环境变量、打开的文件描述符、信号量、虚拟地址空间、代码、数据等。线程也有自己的程序计数器、栈空间和寄存器等。
更具体的线程是进程中的一个执行流程一个进程至少包含一个执行线程。在Linux系统中通过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流。
进程是资源分配的最小单位而线程是程序运行的最小单位。多进程由于进程间是互不干扰的每个进程都有自己的用户虚拟空间可靠性高但数据共享方面就会比较复杂需要用到 IPC通信机制。而多线程则相对容易因为同一进程下的多个线程通信容易它们处于同一个虚拟空间下。
线程有很多标准现在最常用的就算POSIX线程标准注意这个是标准而不是实现。
还可以参考的标准比如openmp线程标准。
线程标识也就是线程IDpthread_t
线程相关的常用函数
pthread_equal() 注意这个函数使用时编译链接要添加 -pthread 选项。
比较两个线程的 ID 是否相等。
pthread_self(): 获取当前的线程标识。
线程的创建:pthread_create()
pthread_create(): pthread_create是类Unix操作系统Unix、Linux、Mac OS X等的创建线程的函数。它的功能是创建线程实际上就是确定调用该线程函数的入口点在线程创建以后就开始运行相关的线程函数。
这个函数需要我们传入一些参数包括 指向线程标识符的指针这个标识符是用来唯一标识该线程的。 用来设置线程属性的参数这个参数可以用来设置新线程的一些属性比如线程的堆栈大小、线程的优先级等。 线程运行函数的起始地址这个参数是要在线程中运行的函数的地址。 运行函数的参数这些参数会被传递给在线程中运行的函数。 当pthread_create函数成功创建一个新线程时它会返回0如果创建线程失败它会返回一个错误码。
新创建的线程会从我们传给pthread_create函数的函数地址开始运行。这个函数被称为线程函数是新线程的入口点。
来写个例子感受一下 这里我们没有打印出创建出来的线程内容这是因为线程的调度取决于系统的调度器策略。
这里子线程还没有来得及被调度主线程就已经结束了主线程除了作为线程的身份还有一个身份就是是线程所在的进程进程都没了那子线程肯定就没了呀所以看不到子线程打印的内容。
所以我们不能凭空猜测父子线程或者进程谁先开始运行嗷。
线程的终止
线程的终止有三种方式 1、 线程从启动例程中返回返回值为线程的退出码 2、 线程可以被同一进程中的其它线程取消 3、 线程调用pthread_exit()函数 pthread_exit(): 在Linux的线程库中pthread_exit函数用于终止调用它的线程并返回一个指向某个值的指针。这个返回值可以被其他线程通过pthread_join函数获取。
具体来说pthread_exit函数的作用是结束当前线程的执行并返回一个指向某个值的指针。这个返回值可以被其他线程通过pthread_join函数获取。当线程调用pthread_exit函数时该线程将立即停止执行但其线程ID和一些资源如打开的文件描述符将保留直到其他线程通过pthread_join函数将其终止。
需要注意的是pthread_exit函数与普通的return语句有所不同。普通的return语句会返回一个值但不会终止当前线程的执行。而pthread_exit函数会立即终止当前线程的执行并返回一个指向某个值的指针。
此外如果主线程没有正确地调用pthread_join函数来等待其他线程结束那么这些线程将不会自动被终止。即使主线程已经执行完毕这些未被终止的线程仍会继续执行直到它们自然结束或被其他线程通过pthread_join函数终止。因此在使用pthread_exit函数时需要注意正确地调用pthread_join函数来等待其他线程结束以避免资源泄漏和数据不一致等问题。
所以这里就必然要提到另一个函数pthread_join(): 在Linux的线程库中pthread_join函数用于等待一个线程的结束。它的作用是阻塞当前线程直到指定的线程终止然后返回该线程的退出状态。
具体来说pthread_join函数需要传入两个参数要等待的线程的标识符和一个指向pthread_exit函数的返回值的指针。当调用pthread_join函数时当前线程将被阻塞直到指定的线程终止。当指定的线程终止时pthread_join函数将返回该线程的退出状态这个返回值可以被存储在指向pthread_exit函数的返回值的指针中。
需要注意的是pthread_join函数并不会立即终止指定的线程而是等待它自然结束或被其他线程通过pthread_exit函数终止。因此在使用pthread_join函数时需要注意正确地管理线程的生命周期确保在需要的时候调用pthread_exit函数来结束线程。
此外如果多个线程同时调用pthread_join函数来等待同一个线程的结束那么只有其中一个线程会得到该线程的退出状态而其他线程将继续阻塞。因此在使用pthread_join函数时需要注意避免竞争条件和死锁等问题。
总的来说pthread_join函数是用来等待一个线程结束的函数并返回该线程的退出状态。它是多线程编程中常用的函数之一可以帮助我们实现线程间的同步和资源管理。
一个小例子 可以看见和我们预料到的一样程序先执行主线程打印Begin然后开启了一个子线程此时主线程因为调用了pthread_join被阻塞在等待子线程的结束然后子线程打印working正常退出后主线程给子线程收完尸打印了end结束程序。
栈的清理:pthread_cleanup_push() 和 pthread_cleanup_pop()
关于栈的清理有两个函数 pthread_cleanup_push() 和 pthread_cleanup_pop(): 在Linux的线程库中pthread_cleanup_push和pthread_cleanup_pop函数用于管理线程的清理操作。它们提供了一种机制用于在线程结束时执行一些清理操作以确保资源被正确释放和避免内存泄漏等问题。
具体来说pthread_cleanup_push函数用于将一个清理函数压入当前线程的清理堆栈中。这个清理函数将在线程结束时被自动调用。当线程结束时系统会检查清理堆栈并依次调用其中的清理函数。这些清理函数可以执行一些必要的资源释放操作如关闭文件描述符、释放内存等。
pthread_cleanup_push函数的原型如下
void pthread_cleanup_push(void (*func)(void *arg), void *arg);其中func是指向清理函数的指针arg是传递给清理函数的参数。
与pthread_cleanup_push函数相反pthread_cleanup_pop函数用于从当前线程的清理堆栈中弹出最近的清理函数并调用它。这个函数通常在另一个函数中调用用于执行一些特定的清理操作。
pthread_cleanup_pop函数的原型如下
void pthread_cleanup_pop(int execute);其中execute是一个标志位用于指示是否要立即执行清理函数。如果execute为0则清理函数将被保留在堆栈中直到线程结束时才被执行如果execute为1则清理函数将被立即执行。
需要注意的是当线程结束时系统会自动调用所有压入清理堆栈的清理函数。因此在使用pthread_cleanup_push和pthread_cleanup_pop函数时需要注意正确地管理线程的生命周期确保在需要的时候调用pthread_cleanup_pop函数来执行清理操作避免出现资源泄漏和数据不一致等问题。
来写个小例子感受一下 执行顺序是321这符合堆栈特性。
但实际上pthread_cleanuo_pop放在函数中的哪个位置都行只要符合基本的语法规范能够和pthread_cleanup_push的左大括号相匹配上即可如上图中的程序还可以这么写 从上图可以看到pop函数出现在pthread_exit()之后理论上这三个函数都不会再被执行了但要注意pthread_cleanup_push和pop并非真正的函数它们是宏定义只会在预处理过程中被文本替换掉所以上图的程序不会报错三个pop函数也会被执行只不过其参数值就看不到了会执行默认的 1 操作也就是执行清理堆栈的函数 可以看见运行结果一模一样。
线程的取消选项
pthread_cancel()
涉及到的函数为pthread_cancel():
在Linux的线程库中pthread_cancel函数用于取消指定线程的执行。它的作用是终止正在执行的线程并释放其占用的资源。
具体来说pthread_cancel函数需要传入一个线程标识符作为参数以指定要取消的线程。当调用pthread_cancel函数时当前线程将尝试取消指定线程的执行。如果成功该线程将被终止并且其占用的资源将被释放。然而需要注意的是由于线程的执行是异步的因此有时pthread_cancel函数可能会失败而线程仍然继续执行。
需要注意的是pthread_cancel函数的使用需要谨慎。如果取消一个正在执行关键任务的线程可能会导致数据不一致或其他不可预知的问题。因此在使用pthread_cancel函数时需要仔细考虑线程的生命周期和任务性质确保在合适的时机使用该函数来避免潜在的风险。
此外需要注意的是被取消的线程的退出状态将被设置为PTHREAD_CANCELED可以通过pthread_join函数来获取这个状态。如果其他线程尝试对已经被取消的线程进行操作如读取其状态或等待其结束则会导致未定义的行为。
总的来说pthread_cancel函数是用来取消指定线程的执行的函数可以用于实现多线程编程中的线程间控制和资源管理。但需要注意正确使用该函数以避免潜在的风险和问题。
取消有两种状态允许和不允许。
允许取消又分为异步cancel 和 推迟cancel推迟cancel是默认的即推迟到cancel点才cancel。
cancel点的概念POSIX定义的cancel点都是可能引发阻塞的系统调用。
在cancel这块可以有一些函数来使用pthread_setcanceltype和pthread_setcancelstate
pthread_setcanceltype()
在Linux的线程库中pthread_setcanceltype函数用于设置线程的取消类型。它的作用是允许线程在接收到取消信号时以特定的方式进行取消操作。
具体来说pthread_setcanceltype函数需要传入两个参数取消类型和旧取消类型。取消类型指定了线程在接收到取消信号时的行为方式而旧取消类型则返回之前的取消类型。
以下是常见的取消类型 PTHREAD_CANCEL_ENABLE使能取消。当线程设置为这种类型时它可以接收到取消信号并执行相应的取消操作。 PTHREAD_CANCEL_DISABLE禁止取消。当线程设置为这种类型时它将忽略取消信号并继续执行。 需要注意的是取消类型是线程级别的属性每个线程都有独立的取消类型设置。因此在使用pthread_setcanceltype函数时需要确保对每个线程进行正确的设置以避免出现意外的问题。
此外需要注意的是取消信号是异步的即线程在执行期间可能会在任何时候接收到取消信号。如果线程被取消其退出状态将被设置为PTHREAD_CANCELED可以通过pthread_join函数来获取这个状态。
总的来说pthread_setcanceltype函数是用来设置线程的取消类型的函数可以用于实现多线程编程中的线程间控制和资源管理。但需要注意正确使用该函数以避免潜在的风险和问题。
pthread_setcancelstate()
在Linux的线程库中pthread_setcancelstate函数用于设置线程的取消状态。它的作用是允许线程在接收到取消信号时以特定的方式进行取消操作。
具体来说pthread_setcancelstate函数需要传入两个参数取消状态和旧取消状态。取消状态指定了线程在接收到取消信号时的行为方式而旧取消状态则返回之前的取消状态。
以下是常见的取消状态 PTHREAD_CANCEL_ENABLE使能取消。当线程设置为这种状态时它可以接收到取消信号并执行相应的取消操作。 PTHREAD_CANCEL_DISABLE禁止取消。当线程设置为这种状态时它将忽略取消信号并继续执行。 需要注意的是取消状态是线程级别的属性每个线程都有独立的取消状态设置。因此在使用pthread_setcancelstate函数时需要确保对每个线程进行正确的设置以避免出现意外的问题。
此外需要注意的是取消信号是异步的即线程在执行期间可能会在任何时候接收到取消信号。如果线程被取消其退出状态将被设置为PTHREAD_CANCELED可以通过pthread_join函数来获取这个状态。
总的来说pthread_setcancelstate函数是用来设置线程的取消状态的函数可以用于实现多线程编程中的线程间控制和资源管理。但需要注意正确使用该函数以避免潜在的风险和问题。
pthread_testcancel() 在Linux的线程库中pthread_testcancel函数用于测试当前线程是否已被取消。它的作用是在线程执行过程中检查是否已经收到了取消信号并在收到取消信号时执行相应的取消操作。
具体来说pthread_testcancel函数会检查当前线程的取消状态和取消类型。如果取消状态被设置为PTHREAD_CANCEL_ENABLE并且取消类型不是PTHREAD_CANCEL_DISABLE那么函数将返回一个非零值表示线程已经被取消。否则函数将返回零表示线程尚未被取消。
需要注意的是pthread_testcancel函数并不会阻塞当前线程而是在执行期间检查是否已被取消。因此可以在线程的执行过程中多次调用该函数以便及时处理取消操作。
此外需要注意的是如果线程已经被取消但其退出状态尚未被获取通过pthread_join函数则调用pthread_testcancel函数将导致未定义的行为。因此在使用pthread_testcancel函数时需要确保已经正确处理了线程的退出状态。
总的来说pthread_testcancel函数是用来测试当前线程是否已被取消的函数可以用于实现多线程编程中的线程间控制和资源管理。但需要注意正确使用该函数以避免潜在的风险和问题。
注意这个函数本身就是一个cancel点所以通俗的说这个函数本身什么也不做就是用来设置一个取消点。
线程分离函数pthread_detach() 在Linux的线程库中pthread_detach函数用于将指定的线程转换为分离状态。处于分离状态的线程在结束时会自动释放其占用的资源而不会等待其他线程对其进行清理操作。
具体来说pthread_detach函数需要传入一个线程标识符作为参数以指定要转换为分离状态的线程。当调用pthread_detach函数时当前线程将尝试将指定线程转换为分离状态。如果成功该线程在结束时将自动释放其占用的资源而不会影响其他线程的执行。
需要注意的是一旦线程被转换为分离状态它将无法被重新设置为可join状态。因此在使用pthread_detach函数时需要谨慎考虑确保不会导致无法挽回的问题。
此外需要注意的是被分离的线程的退出状态将被设置为PTHREAD_EXIT_SUCCESS可以通过pthread_join函数来获取这个状态。但是被分离的线程的资源如内存和文件描述符将立即被释放而不会等待其他线程对其进行清理操作。因此在使用pthread_detach函数时需要确保正确处理线程的资源释放和清理操作。
总的来说pthread_detach函数是用来将指定线程转换为分离状态的函数可以用于实现多线程编程中的资源管理和线程间控制。但需要注意正确使用该函数以避免潜在的风险和问题。
线程竞争和故障
这里我们写个小例子来看一下竞争与故障的问题 我们程序的功能是初始状态下文件/tmp/out中有个1接下来我们创建一堆线程去读写覆盖写这个文件看看竞争与冲突我们先往这个文件中写入1 编写程序
运行结果 为什么为2 这是因为我们的 20 个线程七手八脚的读取这个文件读取完了之后都被沉睡了1秒加sleep的原因是因为机器是单核的无法展示出并发问题所以进行一个模拟等睡醒之后往文件里面进行覆盖写时相当于覆盖写了20次2所以文件中的最后结果为2而不是21这就是线程竞争问题或者叫线程故障问题。
解决线程竞争问题就需要使用到进程同步的内容。
线程同步
线程同步主要使用的就是互斥量也叫互斥锁。 这里涉及到的一个类型是pthread_mutex_t 互斥锁类型
pthread_mutex_init() 和 pthread_mutex_destroy()
初始化互斥量和销毁互斥量pthread_mutex_init和pthread_mutex_destroy。 上图中的pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; 这种初始化方式称为静态初始化与之相对的上面两种函数声明互斥锁的被称为动态初始化。
在Linux下查看man手册时发现终端输出No manual entry for pthread_mutex_init没有此模块。因为线程模块是posix标准的man手册没有安装。
只需在终端安装即可 在终端输入以下命令
sudo apt-get install manpages-posix manpages-posix-dev在Linux的线程库中pthread_mutex_init和pthread_mutex_destroy是用来管理互斥锁mutex的两个函数。互斥锁是一种同步机制用于防止多个线程同时访问共享资源避免产生竞态条件。
pthread_mutex_init函数用于初始化一个互斥锁。它需要传入一个指向互斥锁结构的指针以及一个标志位用于指定互斥锁的类型和属性。以下是常见的互斥锁类型 PTHREAD_MUTEX_NORMAL非递归互斥锁。这种类型的互斥锁不支持递归锁定即同一个线程不能重复获取同一个互斥锁。如果尝试重复获取已经锁定的互斥锁将会导致死锁。 PTHREAD_MUTEX_RECURSIVE递归互斥锁。这种类型的互斥锁支持同一个线程重复获取同一个互斥锁。在释放互斥锁时需要与获取锁的次数相同才能完全释放。 PTHREAD_MUTEX_ERRORCHECK错误检查互斥锁。这种类型的互斥锁在尝试获取已经被锁定的互斥锁时会进行检查并返回错误。 pthread_mutex_init函数的原型如下
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr, int type);其中mutex是指向互斥锁结构的指针attr是指向互斥锁属性的指针通常可以传入NULL表示使用默认属性type是互斥锁的类型。
pthread_mutex_destroy函数用于销毁一个已经初始化的互斥锁。它需要传入一个指向互斥锁结构的指针以释放该互斥锁所占用的资源。以下是pthread_mutex_destroy函数的原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);其中mutex是指向互斥锁结构的指针。
需要注意的是在销毁互斥锁之前确保没有线程正在等待该互斥锁的释放。否则会导致未定义的行为。通常可以在所有线程都已经释放了该互斥锁之后再调用pthread_mutex_destroy函数来销毁它。
pthread_mutex_lock()和pthread_mutex_trylock()和pthread_mutex_unlock() 在Linux的线程库中pthread_mutex_lock、pthread_mutex_trylock和pthread_mutex_unlock是用于管理互斥锁mutex的三个函数。它们分别用于锁定互斥锁、尝试锁定互斥锁和解锁互斥锁。
pthread_mutex_lock函数
int pthread_mutex_lock(pthread_mutex_t *mutex);pthread_mutex_lock函数用于锁定指定的互斥锁。如果该互斥锁已经被锁定则当前线程将阻塞并等待直到该互斥锁变为未锁定状态。当成功获取到互斥锁时该函数将返回0否则将返回一个错误码。
pthread_mutex_trylock函数
int pthread_mutex_trylock(pthread_mutex_t *mutex);pthread_mutex_trylock函数用于尝试锁定指定的互斥锁而不会阻塞当前线程。如果该互斥锁已经被锁定则该函数将立即返回错误码通常是EBUSY而不会等待互斥锁变为未锁定状态。如果成功获取到互斥锁则该函数将返回0否则将返回一个错误码。
pthread_mutex_unlock函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);pthread_mutex_unlock函数用于解锁指定的互斥锁。只有成功获取到该互斥锁的线程才能解锁它。当成功释放互斥锁时该函数将返回0否则将返回一个错误码。需要注意的是在解锁互斥锁之前确保已经成功获取到该互斥锁否则可能会导致未定义的行为。
这些函数的使用可以保证在多线程环境中对共享资源的互斥访问避免竞态条件和数据不一致性的问题。在使用这些函数时需要注意正确地初始化互斥锁对象并在使用完之后及时销毁它以释放资源。
使用互斥锁重写刚刚的线程竞争程序
使用互斥锁的静态初始化
我们重新将该文件的内容写为1 再执行程序 可以看见成功啦
另一个例子程序强化对互斥锁的理解
这个程序的作用是有四个线程这四个线程拼命的往终端上输出abcd一个线程输出a一个线程输出b一个线程输出c还有一个输出d 运行结果如下图 但是现在我们想实现的效果是工工整整的按abcd的效果打印出来而不是上图这样的乱七八糟的改动程序 编译运行 可以看见abcd依次输出。
条件变量
条件变量涉及一个类型pthread_cond_t
在Linux下多线程并发通常使用pthread库来实现。条件变量是pthread库中的一种同步机制用于实现线程间的协调和通信。
条件变量是一种同步原语它允许一个或多个线程等待某个条件成立而其他线程可以通知它们条件已经成立或不再成立。条件变量常常用于解决生产者-消费者问题其中一个生产者线程产生数据并将其放入缓冲区而多个消费者线程从缓冲区消费数据。
在Linux下使用条件变量可以实现以下操作 1、初始化条件变量使用pthread_cond_init函数初始化条件变量。 2、等待条件成立使用pthread_cond_wait函数等待条件成立。线程会进入阻塞状态直到其他线程调用pthread_cond_signal或pthread_cond_broadcast函数通知条件成立或不再成立。 3、通知条件成立使用pthread_cond_signal函数通知等待在条件变量上的一个线程条件已经成立。如果多个线程正在等待则其中一个线程会被唤醒并继续执行。 4、通知所有条件成立使用pthread_cond_broadcast函数通知所有等待在条件变量上的线程条件已经成立。所有等待的线程都会被唤醒并继续执行。 5、销毁条件变量使用pthread_cond_destroy函数销毁条件变量。 在使用条件变量时需要注意以下几点 1、必须在互斥锁的保护下使用条件变量以避免竞态条件。 2、等待和通知操作必须作为一对出现即如果有一个线程正在等待某个条件成立必须有一个或多个线程来通知它条件已经成立。 3、等待操作会阻塞当前线程直到被通知操作唤醒。因此在实现生产者-消费者问题时需要注意确保生产者线程在消费者线程之前先运行以避免死锁。 4、条件变量的使用应该结合具体的业务场景进行设计以避免出现死锁或竞争的情况。 总之条件变量是Linux下多线程并发中常用的一种同步机制它允许线程间进行协调和通信以实现更高效的多线程并发处理。
接下来详细介绍一下每个函数的作用和使用方法
pthread_cond_init这个函数用于初始化一个条件变量。它需要传入两个参数一个是指向pthread_cond_t类型变量的指针另一个是指向pthread_condattr_t类型变量的指针用于设置条件变量的属性。如果条件变量已经初始化则此函数会返回错误。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr);pthread_cond_signal这个函数用于通知等待在条件变量上的一个线程条件已经成立。它需要传入一个pthread_cond_t类型的变量。如果多个线程正在等待则其中一个线程会被唤醒并继续执行。需要注意的是这个函数只通知一个线程如果有多个线程在等待其他线程仍然需要等待。
int pthread_cond_signal(pthread_cond_t *cond);pthread_cond_broadcast这个函数用于通知所有等待在条件变量上的线程条件已经成立。它需要传入一个pthread_cond_t类型的变量。所有等待的线程都会被唤醒并继续执行。
int pthread_cond_broadcast(pthread_cond_t *cond);pthread_cond_destroy这个函数用于销毁一个条件变量。它需要传入一个pthread_cond_t类型的变量。在销毁条件变量之前需要确保没有线程正在等待在条件变量上。
int pthread_cond_destroy(pthread_cond_t *cond);pthread_cond_wait该函数是用于实现线程间的条件变量等待。这个函数允许一个线程等待一个条件变量直到条件变量被满足或被其他线程唤醒。
函数原型如下
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);参数说明 cond指向要等待的条件变量的指针。 mutex指向保护条件变量的互斥锁的指针。在调用pthread_cond_wait之前线程需要获取这个互斥锁。 函数返回值 如果函数成功执行返回0 如果函数执行失败返回一个错误码。 注意事项 在调用pthread_cond_wait之前线程需要先获取互斥锁以避免竞态条件。 pthread_cond_wait函数会释放互斥锁并在等待期间进入阻塞状态。当条件变量被满足或被其他线程唤醒时线程将被唤醒并重新获取互斥锁。 如果线程在等待期间被打断如收到信号则函数将返回错误码EINTR。此时可能需要重新调用pthread_cond_wait函数或其他操作来处理异常情况。 在使用条件变量时需要注意死锁问题。如果一个线程在等待条件变量时一直无法获取互斥锁或条件变量不被满足则可能导致死锁。因此需要合理设计程序以确保线程间的同步和避免死锁情况。 pthread_cond_timewait该函数用于实现线程间的条件变量等待。这个函数允许一个线程等待一个条件变量并在指定的时间内保持等待状态。如果条件变量在等待时间内被满足线程将被唤醒并继续执行否则线程将保持等待状态直到时间到达或被其他线程唤醒。
函数原型如下
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);参数说明 cond指向要等待的条件变量的指针。 mutex指向保护条件变量的互斥锁的指针。在调用pthread_cond_timewait之前线程需要获取这个互斥锁。 abstime指向一个timespec结构体的指针用于指定等待的绝对时间。该时间是从1970年1月1日UTC开始的秒数和纳秒数。如果该参数为NULL则函数会一直等待直到条件变量被唤醒。 函数返回值 如果函数成功执行返回0 如果函数执行失败返回一个错误码。 注意事项 在调用pthread_cond_timewait之前线程需要先获取互斥锁以避免竞态条件。 当函数返回时互斥锁已经被释放。如果线程需要在条件满足后继续执行需要重新获取互斥锁。 如果条件在等待时间内被满足线程将被唤醒并继续执行否则线程将保持等待状态直到时间到达或被其他线程唤醒。 如果线程在等待期间被打断如收到信号则函数将返回错误码EINTR。 以上就是关于Linux下多线程并发时条件变量的详细解释和函数介绍。在使用条件变量时一定要注意保护好条件变量避免出现竞态条件和死锁等问题。同时结合具体的业务场景进行设计也是非常重要的。 我们使用条件变量来改写之前的疯狂输出abcd的例子我们原先是使用了锁链机制来解决的线程竞争问题这里我们使用条件变量来使用通知法解决该问题 这里有一点对于thr_func函数中的注释补充 编译运行 可以看见完全正常。
信号量
在Linux中多线程并发中的信号量是一个整型变量用于控制对共享资源的访问。它是一种同步机制用于解决并发程序中的同步和互斥问题。
信号量本质上是一个计数器可以执行两种操作P操作和V操作。P操作也称为wait操作会使信号量的值减一如果此时信号量的值小于0则进程或线程将被阻塞。V操作也称为signal操作会使信号量的值加一并唤醒可能因为等待信号量而被阻塞的进程或线程。
信号量用于协调多个进程包括父子进程对共享数据对象的读/写。它不以传送数据为目的主要是用来保护共享资源信号量、消息队列、socket连接等保证共享资源在一个时刻只有一个进程独享。
在并发编程中信号量被广泛用于避免竞争条件和死锁问题。通过使用信号量程序员可以确保在任何时候只有一个进程或线程访问特定的共享资源从而避免数据不一致和其他并发问题。
需要注意的是在使用信号量时程序员需要谨慎地设计程序以避免死锁和其他并发问题。同时也需要对信号量的使用进行充分的测试和验证以确保程序的正确性和可靠性。
读写锁
读写锁也称为共享-独占锁是一种特殊的自旋锁它把对共享资源的访问者划分成读者和写者。读者只对共享资源进行读访问写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言能提高并发性因为在多处理器系统中它允许同时有多个读者来访问共享资源最大可能的读者数为实际的逻辑CPU数。写者是排他性的一个读写锁同时只能有一个写者或多个读者与CPU数相关但不能同时既有读者又有写者。
读写锁适合于对数据结构的读次数比写次数多很多的场合。它与互斥锁相比能提高并发性因为在多处理器系统中它允许同时有多个读者来访问共享资源。读写锁的状态有三种读模式下加锁共享、写模式下加锁独占以及不加锁。一次只有一个线程可以占有写模式下的读写锁但是多个线程可以同时占有读模式下的读写锁。读写锁在写加锁状态时其他试图以写模式加锁的线程都会被阻塞。读写锁在读加锁状态时如果有线程希望以写模式加锁时必须阻塞直到所有线程释放锁
简单的理解就是读锁-共享锁写锁-互斥锁
线程属性
注意这块内容其实了解即可因为工作当中百分之八十的内容线程属性都是默认为NULL即可。
还记得之前学过的pthread_create那个函数吧我们在第二个参数设置线程属性时一直填的NULL哪怕就是实际工作当中其实百分之八十以上的时间都是填NULL不过这里还是要对其进行一个解释。
对于线程属性我们有一些函数需要学习
线程属性是指一组描述线程行为的属性可以通过设置这些属性来控制线程的行为。以下是与线程属性相关的函数
pthread_attr_init初始化一个线程属性对象并返回0表示成功否则返回错误码。
#include pthread.h
int pthread_attr_init(pthread_attr_t *attr);pthread_attr_setstacksize设置线程的栈大小。
#include pthread.h
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);pthread_attr_getstacksize获取线程的栈大小。
#include pthread.h
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);pthread_attr_setdetachstate设置线程的分离状态。分离状态是指当线程结束时是否保留资源如果设置为PTHREAD_CREATE_JOINABLE则线程结束时不会立即释放资源其他线程可以通过pthread_join来获取线程的退出状态如果设置为PTHREAD_CREATE_DETACHED则线程结束时会立即释放资源。
#include pthread.h
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);pthread_attr_getdetachstate获取线程的分离状态。
#include pthread.h
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);pthread_attr_setschedparam设置线程的调度参数包括优先级、CPU亲和性等。
#include pthread.h
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *schedparam);pthread_attr_getschedparam获取线程的调度参数。
#include pthread.h
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *schedparam);pthread_attr_setscope设置线程的竞争范围PTHREAD_SCOPE_SYSTEM表示系统级竞争范围PTHREAD_SCOPE_PROCESS表示进程级竞争范围。
#include pthread.h
int pthread_attr_setscope(pthread_attr_t *attr, int scope);写一个小例子来展示系统最多能创建有多少线程 运行结果 在这个程序的基础上然后我们使用线程属性来进行操作 运行结果一样可能是因为本身栈大小就是1M吧…俺目前也不知道为什么。
线程同步的属性
互斥量属性
在多线程编程中有时我们希望对互斥量的行为进行一些定制。例如我们可能希望设置互斥量的类型例如进程间互斥或适应型互斥或者设置一些与互斥量相关的属性例如是否可以嵌套。这就是pthread_mutexattr_init函数的作用。
pthread_mutexattr_init函数允许我们创建一个互斥量属性对象然后我们可以使用这个对象来设置互斥量的各种属性。
以下是pthread_mutexattr_init函数的原型
#include pthread.h int pthread_mutexattr_init(pthread_mutexattr_t *attr);这个函数接收一个指向pthread_mutexattr_t类型变量的指针该变量用于存储互斥量属性。函数返回0表示成功返回错误代码表示失败。
一旦我们使用pthread_mutexattr_init初始化了互斥量属性对象我们可以使用其他函数如pthread_mutexattr_settype或pthread_mutexattr_setnested来设置特定的属性。当我们不再需要互斥量属性对象时我们应该使用pthread_mutexattr_destroy函数来清理它。
需要注意的是虽然使用互斥量属性可以提供更多的灵活性但它们也会使代码变得更复杂因此只在必要时使用。例如对于大多数应用程序使用默认的互斥量类型和属性就足够了
以下是这些函数的详细描述和函数原型
pthread_mutexattr_init此函数用于初始化一个互斥量属性对象以便后续使用。函数原型为
int pthread_mutexattr_init(pthread_mutexattr_t *attr);参数attr是一个指向pthread_mutexattr_t类型变量的指针用于存储互斥量属性。
pthread_mutexattr_settype此函数用于设置互斥量的类型。类型可以是PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_RECURSIVE、PTHREAD_MUTEX_ERRORCHECK等。函数原型为
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);参数attr是一个指向pthread_mutexattr_t类型变量的指针存储了互斥量属性。type是要设置的类型。
pthread_mutexattr_setnested此函数用于设置互斥量的嵌套属性。如果设置为PTHREAD_MUTEX_NESTED则允许多个线程嵌套地获取同一个互斥量。函数原型为
int pthread_mutexattr_setnested(pthread_mutexattr_t *attr, int nested);参数attr是一个指向pthread_mutexattr_t类型变量的指针存储了互斥量属性。nested是要设置的嵌套属性。
pthread_mutexattr_destroy此函数用于销毁一个互斥量属性对象。函数原型为
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);参数attr是一个指向pthread_mutexattr_t类型变量的指针存储了互斥量属性。
pthread_mutexattr_getpshared函数用于获取互斥量的共享属性。它的原型如下
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);参数说明 attr指向互斥量属性对象的指针。 pshared指向共享属性变量的指针用于存储获取到的共享属性值。 共享属性表示互斥量是否可以由多个进程共享。如果共享属性为PTHREAD_MUTEX_PROCESS_SHARED则表示互斥量可以被多个进程共享如果共享属性为PTHREAD_MUTEX_PROCESS_PRIVATE则表示互斥量只能被同一个进程内的线程共享。
pthread_mutexattr_setpshared函数用于设置互斥量的共享属性。它的原型如下
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);参数说明 attr指向互斥量属性对象的指针。 pshared要设置的共享属性值可以是PTHREAD_MUTEX_PROCESS_SHARED或PTHREAD_MUTEX_PROCESS_PRIVATE。 设置共享属性的目的是确定互斥量是进程间共享还是仅在同一个进程内的线程之间共享。根据具体的应用场景和需求可以选择适当的共享属性。
这些函数的使用可以让我们在创建和管理互斥量时有更多的灵活性和控制力以满足特定的多线程编程需求。请注意这些函数的返回值通常为0表示成功否则表示错误。
为什么需要这两个函数是因为有时候我们会需要进行跨进程的操作比如使用了clone系统调用时。
补充clone系统调用 在Linux中clone系统调用是一种用于创建新进程的机制它允许在创建新进程时指定共享部分和独立部分。
clone系统调用接受三个参数新进程的返回值一个__clone_args结构体指针以及一个进程创建时的标志位。
__clone_args结构体包含了创建新进程时需要用到的各种参数包括 new_stack指定新进程的栈地址。如果设置为NULL则使用当前线程的栈地址。 ptid指向一个pthread_t结构的指针用于存储新进程的线程ID。 tls指向新进程的线程本地存储(TLS)的指针。如果设置为NULL则使用当前线程的TLS。 exit_thread一个函数指针指向新进程退出时的处理函数。如果设置为NULL则使用默认的处理函数。 arg一个指向传递给新进程的参数的指针。 进程创建时的标志位用于指定新进程的类型和行为。常用的标志位包括 CLONE_VM共享虚拟内存空间。 CLONE_FS共享文件系统描述符。 CLONE_FILES共享文件描述符。 CLONE_SIGHAND共享信号处理函数。 CLONE_PID进程ID相同。 通过组合这些标志位可以使用clone系统调用创建出具有不同特性的新进程。
下面是一个使用clone系统调用的示例代码
#include stdio.h
#include stdlib.h
#include pthread.h
#include unistd.h void *child_func(void *arg) { printf(This is the child process.\n); exit(0);
} int main() { pthread_t tid; __clone_args args {NULL, tid, NULL, NULL, child_func, NULL}; int clone_flags CLONE_VM | CLONE_FS | CLONE_FILES; pid_t pid clone(args, NULL, clone_flags); if (pid -1) { perror(clone); exit(EXIT_FAILURE); } else if (pid 0) { // child process printf(This is the child process.\n); exit(0); } else { // parent process printf(This is the parent process.\n); waitpid(pid, NULL, 0); exit(EXIT_SUCCESS); }
}在上面的示例中我们使用clone系统调用创建了一个新进程并指定了共享虚拟内存空间、文件系统描述符和文件描述符的标志位。新进程的返回值存储在tid变量中并通过调用pthread_join等待新进程的结束。在父进程中我们使用waitpid等待子进程的结束并使用返回值退出父进程。在子进程中我们执行自定义的函数child_func并退出子进程。
条件变量属性
和互斥量属性的内容大差不差不再赘述。 基本上用默认的属性就能够完成百分之八十以上的任务了。
可重入概念
多线程中的IO
这里提到的可重入主要是指多线程中的IO机制。
我们的多线程中的 IO 函数都是默认已经实现了多线程的安全并发所以我们不会比如说下面有123个线程都在调用puts函数来打印东西 其打印结果最多就算aaaaabbbbbccccc的排列组合而不会打印abcccacbb等这种乱序如果是这种情况那就说明是可重入的就是因为这里的puts底层实现是有锁机制的对应的POSIX标准也提供了没有线程安全版本的IO函数 如果使用的是这种版本那么就会产生我刚刚说的现象。
线程与信号
在之前说过信号的响应过程中每个进程有一个mask位图和一个pending位图但其实在引入了线程机制的系统中对于一个进程而言只有一个进程级别的pending位图以进程为单位是没有mask位图的然后其内部的每个线程都会有一个mask位图和一个线程级别的pending位图进程级别之间发的信号通过进程级别的pending位图来接收而线程级别之间发送的信号由线程级别的pending位图来接收。
对于线程响应信号时其mask位图会先去与进程级别的pending位图进行按位与从内核态返回用户态时然后再来与线程级别的pending位图进行按位与这才得到最终的信号响应情况这就是线程与信号之间的关系 除了这一点与之前说过的不同其它的响应过程和之前说的都是一样的。
对于这一块我们有几个函数可以使用
pthread_sigmask() pthread_sigmask是一个在Linux下的Pthreads库中的函数用于阻塞或取消阻塞一组信号。这是一个多线程编程中常用的函数尤其是在处理可能会中断线程执行的信号时。
函数的原型如下
int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);参数说明
1、how这是一个指定如何更改信号屏蔽字的整数。可能的值包括 SIG_BLOCK将newmask指定的信号添加到当前的信号屏蔽字中。 SIG_UNBLOCK从当前的信号屏蔽字中移除newmask指定的信号。 SIG_SETMASK将当前的信号屏蔽字设置为newmask。 2、newmask这是一个指向信号集合的指针该集合包含了我们希望添加或移除的信号。如果此参数为NULL那么将不会更改信号屏蔽字。
3、oldmask这是一个指向信号集合的指针用于存储调用pthread_sigmask之前的信号屏蔽字。如果此参数为NULL那么将不会返回旧的信号屏蔽字。
返回值如果成功函数返回0如果失败返回错误码。
使用这个函数可以帮助我们在多线程环境中更好地控制信号的处理。例如你可能希望在某些关键的代码段中阻止某些信号以防止这些信号中断你的线程。在代码段执行完毕后你可以恢复原来的信号屏蔽字以允许这些信号再次中断你的线程。
需要注意的是虽然这个函数的名字中包含pthread但它实际上操作的是调用线程的整个进程的信号屏蔽字而不仅仅是线程自己的信号屏蔽字。这意味着对信号屏蔽字的更改将影响到进程中的所有线程。因此在使用这个函数时需要谨慎以避免引入难以调试的并发问题。
sigwait() 在Linux下的多线程编程中sigwait函数是一个用于处理信号的函数。它的作用是等待指定的信号发生并在接收到信号后执行相应的操作。
sigwait函数的原型如下
int sigwait(const sigset_t *set, int *sig);参数说明 set一个指向信号集的指针该信号集包含我们希望等待的信号。 sig一个指向整数的指针用于存储接收到的信号的值。 sigwait函数会一直等待直到信号集set中的某个信号发生。当接收到信号时函数将返回0并将接收到的信号值存储在sig所指向的整数中。如果函数返回-1则表示出现错误。
使用sigwait函数可以让我们在多线程中以一种阻塞的方式等待特定的信号发生。这对于需要协同多个线程工作的程序非常有用例如当一个线程需要等待另一个线程发送的特定信号时可以使用sigwait函数来实现这种同步。
需要注意的是sigwait函数并不改变信号掩码的阻塞与非阻塞状态。它只是等待指定的信号发生并将其从进程的未决队列中取走。如果要确保sigwait线程接收到信号其他所有线程包括主线程和sigwait线程必须将该信号阻塞以确保该信号处于未决状态。
综上所述sigwait函数是Linux下多线程编程中用于等待特定信号发生的一种机制可以用于实现线程间的同步和协调操作。
pthread_kill() pthread_kill函数是在Linux下多线程编程中使用的一个函数用于向指定的线程发送信号。它的原型如下
int pthread_kill(pthread_t thread, int sig);参数说明 thread一个指向要发送信号的线程的pthread_t类型的指针。 sig要发送的信号的值。 pthread_kill函数将信号sig发送给线程thread。如果该线程是可接收信号的则会执行与该信号关联的操作。如果函数成功则返回0否则返回一个错误码。
使用pthread_kill函数可以在多线程程序中实现线程间的通信和协调。例如当一个线程需要通知另一个线程执行特定任务时可以使用pthread_kill函数向该线程发送一个特定的信号以触发相应的操作。
需要注意的是要确保发送信号的线程和接收信号的线程之间的同步和协调以避免出现竞争条件和未定义的行为。此外还需要根据具体的程序需求和设计来决定何时使用pthread_kill函数以及如何处理接收到的信号。
综上所述pthread_kill函数是Linux下多线程编程中用于向指定线程发送信号的函数可以用于实现线程间的通信和协调。在使用时需要注意同步和设计问题以确保程序的正确性和稳定性。
线程与fork
在Linux下多线程编程中fork()函数的使用和多线程之间存在一些特定的关系和注意事项。
首先fork()函数在多线程编程中的使用可能会导致一些问题。当在多线程中调用fork()函数时子进程会继承父进程的所有资源包括线程。这意味着在子进程中会有和父进程相同数量的线程包括调用fork()函数的线程。然而在子进程中除调用线程外的其它线程全都终止执行并消失。这意味着如果在父进程中有多个线程而在其中一个线程调用了fork()函数那么在子进程中除了调用线程外其他线程将会消失。
这种行为可能会导致死锁和内存泄露的情况。因为当多线程程序在fork()之后进行时如果其中一个线程在其他线程之前结束那么它可能会试图访问已经被其他线程占用的资源从而引起死锁。同时如果子进程继承了父进程的内存空间而子进程比父进程先结束那么这部分内存空间将不会被释放从而引起内存泄露。
因此在进行多线程编程时尽量避免在多线程环境中使用fork()函数。如果必须使用fork()函数那么应该在调用fork()函数之前避免创建线程因为这会影响到全局对象的安全初始化。同时也需要注意在父子进程之间进行同步和协调以确保它们的正确性和稳定性。
综上所述fork()函数的使用和多线程之间存在一些特定的关系和注意事项。在进行多线程编程时应该尽量避免在多线程环境中使用fork()函数并注意父子进程之间的同步和协调以确保程序的正确性和稳定性。