一个正在运行的函数
Posix 线程是一套标准,而不是实现。另外还有 openmp 线程等
Posix 线程标识:pthread_t
@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note$ ps axm
gaowanlu
PID TTY STAT TIME COMMAND1 ? - 0:00 /init
- - Ssl 0:00 -
- - Ssl 0:00 -
6 ? - 0:00 plan9 --control-socket 6 --log-level 4 --server-fd 7 --pipe-fd 9 --socket-path /mnt/c/Users/gaowanlu/AppData/Local/Package
- - Sl 0:00 -//线程
- - Sl 0:00 -//线程
9 tty1 - 0:00 /init
- - Ss 0:00 -
10 tty1 - 0:00 -bash
- - S 0:00 -
145 tty1 - 0:00 ps axm
- - R 0:00 -
//一个进程至少一个线程
其实线程是 LWP,轻量级进程
//-L 为以Linux形式查看
@DESKTOP-QDLGRDB:/$ ps axm -L
gaowanlu
PID LWP TTY STAT TIME COMMAND1 - ? - 0:00 /init
- 1 - Ssl 0:00 -
- 8 - Ssl 0:00 -
6 - ? - 0:00 plan9 --control-socket 6 --log-level 4 --server-fd 7 --pipe-fd 9 --socket-path /mnt/c/Users/gaowanlu/AppData/Local/Packages/CanonicalGroupLimited.Ubuntu20.04onWindows_
- 6 - Sl 0:00 -
- 7 - Sl 0:00 -
9 - tty1 - 0:00 /init
- 9 - Ss 0:00 -
10 - tty1 - 0:00 -bash
- 10 - S 0:00 -
160 - tty1 - 0:00 ps axm -L
- 160 - R 0:00 -
进程其实就是容器,用来存储线程
pthread_equal 用来比较两个线程标识是否相同
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
pthread_self 是一个 POSIX 线程库函数,它返回调用线程的线程 ID(Thread ID)。线程 ID 是一个唯一的无符号长整型(pthread_t 类型),用于标识线程。该函数的原型如下:
#include <pthread.h>
pthread_t pthread_self(void);
线程 ID 在其生命周期中始终保持不变。因此,在调用 pthread_create 函数创建线程后,您可以使用 pthread_self 函数在新线程中获取其线程 ID。
pthread_create 函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
//创建成功返回0 否则返回1
// thread是指向线程标识符的指针
// attr是一个指向线程属性的指针
// start_routine是一个指向线程函数的指针
// arg是传递给线程函数的参数。
样例
#include <iostream>
#include <pthread.h>
#include <stdio.h>
using namespace std;
void *message(void *message)
{
char *str = (char *)message;
<< str << endl;
cout return nullptr;
}
int main(int argc, char **argv)
{
pthread_t thread1, thread2;
const char *message1 = "thread1";
const char *message2 = "thread2";
int ret1, ret2;
= pthread_create(&thread1, nullptr, message, (void *)message1);
ret1 if (ret1)
{
("Error - pthread_create() return : %s\n", strerror(ret1));
printfreturn 1;
}
= pthread_create(&thread2, nullptr, message, (void *)message2);
ret2 if (ret2)
{
("Error - pthread_create() return : %s\n", strerror(ret2));
printfreturn 1;
}
// wait for thread
(thread1, nullptr);
pthread_join(thread2, nullptr);
pthread_joinreturn 0;
}
// gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ ./main
// thread1
// thread2
线程的调度取决与调度器策略
线程终止的方式
3 种方式:
(1) 线程从启动例程返回,返回值就是线程的退出码
(2) 线程可以被同一进程中的其他线程取消
(3) 线程调用 pthread_exit 函数
#include <pthread.h>
void pthread_exit(void *retval);
样例
#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
using namespace std;
void *message(void *message)
{
char *str = (char *)message;
<< str << endl;
cout (nullptr);
pthread_exit}
int main(int argc, char **argv)
{
pthread_t thread1;
const char *message1 = "thread1";
int ret1;
= pthread_create(&thread1, nullptr, message, (void *)message1);
ret1 if (ret1)
{
("Error - pthread_create() return code: %s\n", strerror(ret1));
printfreturn 1;
}
// wait for thread
(thread1, nullptr);
pthread_joinreturn 0;
}
pthread_join() 函数是一个线程函数,用于等待指定线程的终止,并将线程的退出状态存储在一个指定的变量中。该函数会阻塞当前线程,直到指定的线程退出为止。
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
//thread 是要等待的线程的标识符;retval 是一个指向指针的指针,用于存储线程的退出状态。
//return: 0成功否则失败
//retval就是pthread_exit传的内容
如果指定的线程没有以 joinable 的状态创建,或者已经被其他线程 join,或者调用线程自己已经被取消,那么 pthread_join() 函数都会失败并返回一个非零值。
使用 pthread_join() 函数时,需要注意以下几点:
调用 pthread_join()
函数的线程会被阻塞,直到指定的线程退出为止。
如果不关心线程的退出状态,可以将 retval 参数设置为 NULL。
线程只能被 join 一次,如果尝试多次 join 同一个线程,或者 join
已经被其他线程 join 的线程,都会导致失败。
如果线程被设置为 detached 状态,则不能使用 pthread_join()
函数等待它的退出,否则会失败。
样例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void *thread_func(void *arg)
{
("Thread running\n");
printfint *res = (int *)malloc(sizeof(int));
*res = 42;
((void *)res);
pthread_exit}
int main()
{
pthread_t thread;
int ret;
void *retval;
= pthread_create(&thread, NULL, thread_func, NULL);
ret if (ret != 0)
{
("pthread_create");
perror(EXIT_FAILURE);
exit}
= pthread_join(thread, &retval);
ret if (ret != 0)
{
("pthread_join");
perror(EXIT_FAILURE);
exit}
if (retval)
{
("Thread exited with status %d\n", (*((int *)retval)));
printf(retval);
free}
return 0;
}
/*
gaowanlu@DESKTOP-QDLGRDB:/$ g++ main.cpp -o main -lpthread
gaowanlu@DESKTOP-QDLGRDB:/$ ./main
Thread running
Thread exited with status 42
*/
相关函数 pthread_cleanup_push 和 pthread_cleanup_pop
是一对宏定义,用于在线程中注册清理处理函数
在第七章有类似的钩子函数 atexit,当进程正常终止时可以被逆序调用
重点:pthread_cleanup_push 与 pthread_cleanup_pop 应该成对出现,否则在预处理后会有语法错误
#include <pthread.h>
//挂钩子函数 routine函数指针,arg函数参数
void pthread_cleanup_push(void (*routine)(void *),void *arg);
//从钩子上区内容execute为1则代表取下来且执行调用 为0则取下来不调用
void pthread_cleanup_pop(int execute);
样例
#include <pthread.h>
#include <stdio.h>
void cleanup_handler(void *arg)
{
("Cleanup: %s\n", (char *)arg);
printf}
void *thread_func(void *arg)
{
(cleanup_handler, (void *)"First handler");
pthread_cleanup_push(cleanup_handler, (void *)"Second handler");
pthread_cleanup_push("Thread running\n");
printf(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(NULL);
pthread_exit}
int main()
{
pthread_t thread;
int ret;
= pthread_create(&thread, NULL, thread_func, NULL);
ret if (ret != 0)
{
("pthread_create");
perrorreturn 1;
}
(thread, NULL);
pthread_joinreturn 0;
}
/*
gaowanlu@DESKTOP-QDLGRDB:/$ g++ main.cpp -o main -lpthread
gaowanlu@DESKTOP-QDLGRDB:/$ ./main
Thread running
Cleanup: Second handler
Cleanup: First handler
*/
1、语法规定:pthread_cleanup_push() 和 pthread_cleanup_pop() 是一对宏定义,必须按照规定的语法配对使用。
2、栈管理:pthread_cleanup_push() 和 pthread_cleanup_pop() 实现了一个栈结构,用于存储清理处理函数。pthread_cleanup_push() 将一个清理处理函数入栈,pthread_cleanup_pop() 将最后一个清理处理函数出栈。如果它们不成对出现,会导致栈结构不正确,可能会出现内存泄漏或者错误地清理资源的情况。
3、取消处理:当一个线程被取消时,会按照清理处理函数入栈的顺序依次执行清理处理函数,直到栈为空或者遇到不能被取消的清理处理函数为止。如果 pthread_cleanup_push() 和 pthread_cleanup_pop() 不成对出现,就可能会导致清理处理函数没有按照预期的顺序执行,从而产生错误。
因此,为了避免语法错误和资源泄漏,以及确保清理处理函数能够按照正确的顺序执行,pthread_cleanup_push() 和 pthread_cleanup_pop() 必须成对出现。
pop 甚至可以写到 pthread_exit 的后面
void *thread_func(void *arg)
{
(cleanup_handler, (void *)"First handler");
pthread_cleanup_push(cleanup_handler, (void *)"Second handler");
pthread_cleanup_push("Thread running\n");
printf(NULL);
pthread_exit(0);
pthread_cleanup_pop(0);
pthread_cleanup_pop}
//在这时即使pthread_cleanup_pop,这时的pop仅仅有语法补全的功能,默认当exit时会执行pop,除非是pop(0)才不会被调用
取消有两种状态:允许和不允许
允许取消又分为:异步 cancel、推迟 cancel(默认 推迟到 cancel
点再响应)
cancel 点:POSIX 定义的 cancel 点,都是可能引发阻塞的系统调用
pthread_cancel 函数向目标线程发送一个取消请求
pthread_setcancelstate(): 设置是否允许取消
pthread_setcanceltype(): 设置取消方式
pthread_testcancel(): 本函数什么都不做,就是一个 cancel 点
实际样例
int fd1 = open();//open前是cancel点
if (fd1 < 0)
{
();
perror(nullptr);
pthread_exit}
// cleanup_push(); --> close(fd1);
int fd2 = open();//open前是cancel点
if (fd2 < 0)
{
();
perror(nullptr);
pthread_exit}
// cleanup_push(); --> close(fd2);
函数向目标线程发送一个取消请求
#include <pthread.h>
int pthread_cancel(pthread_t thread);
pthread_cancel 函数是 pthread
线程库提供的一个函数,用于请求取消正在运行的线程。
pthread_cancel
函数可以向指定线程发送一个取消请求,这个请求并不能保证线程会立即被取消,而是告知线程在下一个取消点或异步取消时终止自身的执行。在
POSIX
标准中,每个函数都标记了是否是一个取消点,线程在执行这些函数时可能会检查取消请求。
如果线程在取消点时收到取消请求,那么它会自动终止执行。如果线程不在取消点,那么它会在某个取消点时终止执行。
如果线程被成功取消,那么它会执行一个清理函数(cleanup
handler)列表,这些清理函数可以用 pthread_cleanup_push 和
pthread_cleanup_pop
函数来注册和取消注册。清理函数可以用于释放线程持有的资源,以及执行一些必要的清理操作。
需要注意的是,pthread_cancel
函数并不能保证线程会被立即取消,因为线程可能处于不可取消状态(如执行了信号处理程序时),或者处于阻塞状态(如等待
I/O
操作完成)。此外,线程必须在创建时设置了可以被取消的属性,否则取消请求也不会生效。
因此,在使用 pthread_cancel
函数时需要仔细考虑线程的状态以及对资源的使用,以避免可能的资源泄露和其他问题。
pthread_setcancelstate(): 设置是否允许取消
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
在 POSIX 标准中,每个线程都有一个取消状态(cancelability
state),它决定了线程是否可以被取消。线程的取消状态可以是可取消状态(cancelable
state)或者不可取消状态(uncancelable state)。
pthread_setcancelstate 函数可以设置线程的取消状态,它有两个参数:
state:要设置的取消状态,可以取以下两个值之一:
PTHREAD_CANCEL_ENABLE:表示线程是可取消状态,可以被取消。
PTHREAD_CANCEL_DISABLE:表示线程是不可取消状态,不能被取消。
oldstate:一个指向 int
类型的变量的指针,用于存储原来的取消状态。如果不需要获取原来的状态,可以将这个参数设置为
NULL。
需要注意的是,如果线程处于不可取消状态,那么调用 pthread_cancel
函数也不能取消该线程,直到线程变为可取消状态为止。
在使用 pthread_setcancelstate
函数时,需要仔细考虑线程的取消状态以及对资源的使用,以避免可能的资源泄露和其他问题。一般来说,线程应该尽可能地保持可取消状态,以便能够及时响应取消请求。
pthread_setcanceltype(): 设置取消方式
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
在 POSIX 标准中,线程的取消类型(cancelability
type)决定了线程在取消时执行的动作。线程的取消类型可以是异步取消(asynchronous
cancellation)或者延迟取消(deferred cancellation)。
异步取消:线程在任何时候都可以被取消,并且取消请求会立即终止线程的执行。
延迟取消:线程只在特定点上被取消,也就是在线程执行到取消点时才会响应取消请求。
pthread_setcanceltype 函数可以设置线程的取消类型,它有两个参数:
type:要设置的取消类型,可以取以下两个值之一:
PTHREAD_CANCEL_ASYNCHRONOUS:表示线程的取消类型是异步取消。
PTHREAD_CANCEL_DEFERRED:表示线程的取消类型是延迟取消。
oldtype:一个指向 int
类型的变量的指针,用于存储原来的取消类型。如果不需要获取原来的类型,可以将这个参数设置为
NULL。
需要注意的是,异步取消可能会导致资源泄露和其他问题,因此在使用异步取消时需要非常小心。一般来说,推荐使用延迟取消。
在使用 pthread_setcanceltype
函数时,需要仔细考虑线程的取消类型以及对资源的使用,以避免可能的资源泄露和其他问题。
pthread_testcancel(): 本函数什么都不做,就是一个 cancel 点
#include <pthread.h>
void pthread_testcancel(void);
pthread_testcancel 函数是 pthread
线程库提供的一个函数,用于检查线程是否收到了取消请求,并在收到请求时立即取消线程。
当一个线程处于可取消状态(cancelable
state)时,它可以在任何时候被取消。如果一个线程正在执行一些可能会阻塞的操作,而在等待这些操作完成之前需要检查取消请求,那么就可以使用
pthread_testcancel 函数。
pthread_testcancel
函数会检查当前线程是否收到了取消请求,如果收到了请求,就会立即取消线程。如果没有收到请求,函数将立即返回,并不会对线程产生任何影响。
需要注意的是,只有线程处于可取消状态时,才能够使用 pthread_testcancel
函数。如果线程处于不可取消状态(uncancelable
state),那么取消请求将不会生效,线程也不会被立即取消。
在使用 pthread_testcancel
函数时,需要仔细考虑线程的状态以及对资源的使用,以避免可能的资源泄露和其他问题。一般来说,推荐使用
pthread_testcancel
函数来及时响应取消请求,以便尽可能地避免资源泄露和其他问题。
#include <pthread.h>
int pthread_detach(pthread_t thread);
在使用 pthread 线程库创建线程时,可以使用 pthread_detach
函数将线程标记为“分离状态”,使得线程在退出时自动释放所有资源。
当一个线程被标记为“分离状态”时,它的资源(包括内存、打开的文件描述符等)将在线程结束时自动被系统回收,而不需要其他线程调用
pthread_join 函数等待该线程结束并回收其资源。
在分离状态下,如果其他线程调用 pthread_join
函数等待该线程结束,将会返回错误码 EINVAL(Invalid
argument),因为该线程已经处于分离状态,不再需要其他线程等待它的结束。
需要注意的是,一旦一个线程被标记为“分离状态”,就无法再将它恢复为“非分离状态”,因此在调用
pthread_detach
函数前需要确保该线程不再需要被其他线程等待。同时,需要确保对该线程的所有资源的操作都已经完成,否则可能会出现资源泄露等问题。
概念推荐去看 C++并发编程部分,在此不再展开
线程竞争是指两个或多个线程尝试访问和更新同一个资源的情况。线程竞争会导致程序运行错误,这是因为当多个线程尝试同时访问同一个资源时,可能会导致数据不一致、数据丢失或其他问题。
要避免线程竞争,应该使用互斥量,它允许一个线程访问一个资源,而阻止其他线程访问该资源。如果一个线程正在使用资源,其他线程将被阻塞,直到第一个线程释放该资源。另外,还可以使用信号量来控制对资源的访问。信号量可以控制多个线程可以同时访问某个资源的数量,从而防止线程竞争的发生。
概念推荐去看 C++并发编程部分,在此不再展开
Linux 系统编程中,线程同步是指通过确保多个线程在安全地共享资源时不会发生冲突,以确保程序正确执行的一种技术。一般来说,线程同步技术使用信号量、互斥锁、读者/写者锁、条件变量、信号处理器等方法进行实现。
信号量是一种特殊的整数,用于控制对共享资源的访问。它可以用来实现同步,以确保多个线程可以安全地访问共享资源。互斥锁是一种特殊的锁,用于保护共享资源。它可以保证只有一个线程可以对共享资源进行读写,从而避免多个线程之间的冲突。读者/写者锁也是一种特殊的锁,它可以同时允许多个线程进行读操作,但是只允许一个线程进行写操作。条件变量是一种特殊的变量,可以用来通知主线程有其他线程发生了某种变化。信号处理器也是一种特殊的处理器,用于在线程之间传递信号,以实现同步。
#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t* mutex);
int pthread_mutex_init(pthread_mutex_t* restrict mutex,
const pthread_mutexattr_t* restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);//阻塞获得锁
int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞尝试获取锁,不可获取状态则直接返回
int pthread_mutex_unlock(pthread_mutex_t *mutex);
尝试获取锁,超时返回
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime);
简单样例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
pthread_mutex_t mutex;
void* thread1(void* args)
{
int ret = 0;
int count = 0;
struct timespec abstime;
//设置绝对时间
(CLOCK_REALTIME, &abstime);
clock_gettime.tv_sec += 5;//五秒后
abstimewhile(1)
{
("Thread1 try to get the lock...\n");
printf= pthread_mutex_timedlock(&mutex, &abstime);
ret if(ret != 0)
{
("Thread1 try to get the lock more than 5 second, failed!\n");
printfbreak;
}
++;
count("Thread1 get the lock, count = %d\n", count);
printf(2);
sleep(&mutex);
pthread_mutex_unlock("Thread1 unlock the lock\n");
printf}
(NULL);
pthread_exitreturn NULL;
}
pthread_once()函数是一个多线程编程函数,用于保证某一段代码在多线程环境下只被执行一次。其原型如下:
#include <pthread.h>
int pthread_once(pthread_once_t *once_control,
void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;
该函数接受两个参数:
once_control:一个指向 pthread_once_t
类型变量的指针,该变量用于控制某一段代码是否被执行过。该变量必须初始化为
PTHREAD_ONCE_INIT。
init_routine:一个指向函数的指针,该函数包含需要保证只被执行一次的代码。
pthread_once()函数保证在多线程环境下,init_routine()函数仅被执行一次,无论有多少个线程同时调用该函数。它利用了原子操作和互斥锁等技术来实现。
一般情况下,pthread_once()函数常常用于初始化某些全局变量,或者初始化一些只需要执行一次的操作,比如初始化某个库或系统资源等。
样例
//init函数只会被执行一次
#include <stdio.h>
#include <pthread.h>
pthread_once_t once = PTHREAD_ONCE_INIT;
void init()
{
("Initialization function\n");
printf}
void* thread_func(void* arg)
{
pthread_t thread_id = pthread_self();
("Thread %lu is running\n", thread_id);
printf
(&once, init);
pthread_once
("Thread %lu continues execution\n", thread_id);
printfreturn NULL;
}
int main()
{
pthread_t thread1, thread2;
(&thread1, NULL, thread_func, NULL);
pthread_create(&thread2, NULL, thread_func, NULL);
pthread_create
(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join
return 0;
}
工程推荐 C++实现
https://github.com/crust-hub/tubekit/tree/main/src/thread
在此默认您知道条件变量的概念与基本使用场景
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
wait 就是先解锁,当 cond 满足后,即接收到通知后,会 lock mutex
锁。
传递给 pthread_cond_wait
的互斥量对条件进行保护,调用者把锁住的互斥量传给函数,函数自动将调用线程放到等待条件的线程列表上,对互斥量解锁。当
pthread_cond_wait 返回时,互斥量在此被锁住
pthread_cond_timewait 支持指定愿意等待多长时间
#include <pthread.h>
//超时返回
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
//死等
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);//通知所有等待
int pthread_cond_signal(pthread_cond_t *cond);//通知任意一个
样例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex;
void *thread_func(void *arg)
{
pthread_t thread_id = pthread_self();
("Thread %lu is running\n", thread_id);
printf(&cond, &mutex);
pthread_cond_wait("Thread %lu continues execution\n", thread_id);
printf(&mutex); // 解锁给下一个已经接收到通知的pthread_cond_wait
pthread_mutex_unlockreturn NULL;
}
int main()
{
pthread_t thread1, thread2;
(&thread1, NULL, thread_func, NULL);
pthread_create(&thread2, NULL, thread_func, NULL);
pthread_create(3);
sleep(&cond); // 只让一个pthread_cond_wait接收到通知
pthread_cond_signal// pthread_cond_broadcast(&cond); // 让所有pthread_cond_wait收到通知
(thread1, NULL);
pthread_join(thread2, NULL);
pthread_joinreturn 0;
}
/*
使用pthread_cond_broadcast(&cond);
gaowanlu@DESKTOP-QDLGRDB:/$ ./main
Thread 140021693155072 is running
Thread 140021684700928 is running
Thread 140021693155072 continues execution
Thread 140021684700928 continues execution
*/
/*
使用pthread_cond_signal(&cond);
gaowanlu@DESKTOP-QDLGRDB:/$ ./main
Thread 140459443291904 is running
Thread 140459434837760 is running
Thread 140459443291904 continues execution
//然后卡死了
*/
当 pthread_cond_wait 被通知后,线程会重新尝试获取与条件变量相关联的互斥锁,以便访问共享资源。
如果线程没有抢到锁,它将阻塞在 pthread_cond_wait 函数上,等待互斥锁的释放和通知。当其他线程调用 pthread_cond_signal 或 pthread_cond_broadcast 函数并释放互斥锁时,等待在条件变量上的线程将被唤醒并重新尝试获取互斥锁。
注意,当 pthread_cond_wait 函数被唤醒后,线程需要再次获取互斥锁才能访问共享资源。在获取锁之前,线程仍然是阻塞状态。
如果多个线程在等待条件变量上,当 pthread_cond_signal 或 pthread_cond_broadcast 函数被调用时,可能会有多个线程被唤醒。因此,在使用条件变量时,需要仔细考虑线程同步和竞态条件问题,以避免死锁和数据竞争等问题。
信号量是操作系统中一种用于实现进程间同步和互斥的机制。它是一个计数器,用来记录某个共享资源的可用数量,同时提供了两个原子操作:P 操作和 V 操作。
P 操作(也称为申请或等待操作)会尝试获取一个资源,如果资源的计数器为 0,则会被阻塞,直到资源变得可用。一旦获取到资源,计数器会减 1,表示该资源已被占用。
V 操作(也称为释放或信号操作)会将一个已经占用的资源释放出来,同时将计数器加 1,表示该资源现在可用。
信号量的使用可以避免多个进程同时访问同一个共享资源,从而保证数据的正确性和一致性。在实际应用中,信号量常常被用来解决生产者-消费者问题、读者-写者问题等并发控制问题。
利用互斥量以及条件变量可以封装出自己的信号量,但是第 15 章 进程间通信有专门的信号量主题,在此不再展开
pthread_rwlock_t 是一个 POSIX 线程库提供的读写锁,全称为 Pthreads Read-Write Lock。它可以用于同步多个线程之间的读和写操作。
读写锁是一种特殊的锁,它允许多个线程同时持有读锁,但是只允许一个线程持有写锁。当一个线程持有写锁时,其他线程不能持有读锁或写锁,当一个线程持有读锁时,其他线程可以持有读锁,但不能持有写锁。
#include <pthread.h>
//初始化与销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
//上锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
在读操作前
(&rwlock);
pthread_rwlock_rdlock// 读操作
(&rwlock); pthread_rwlock_unlock
在写操作时
(&rwlock);
pthread_rwlock_wrlock// 写操作
(&rwlock); pthread_rwlock_unlock
使用方式与互斥量的超时返回差不多
#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
POSIX 自旋锁是一种线程同步机制,用于实现对共享资源的互斥访问。它是由 POSIX 标准定义的,因此在 POSIX 兼容的操作系统中可用,如 Linux 和 Unix。
自旋锁是一种基于忙等待的锁,也就是说,当线程请求锁时,如果锁已经被另一个线程持有,该线程将一直循环等待,直到锁被释放。自旋锁相对于其他锁的优点在于它能够避免线程的上下文切换,从而提高锁的性能。然而,如果线程一直在循环等待锁,它将占用 CPU 资源,因此在高并发情况下,自旋锁的使用可能会降低系统的整体性能。
注意:使用自旋锁时需要保证锁的作用范围,以避免锁的滥用和竞争条件的出现。
初始化与销毁
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
// 参数说明:
// lock:指向要初始化的自旋锁的指针。
// pshared:指定自旋锁的共享属性。如果为0,则自旋锁是进程内共享的,如果不为0,则自旋锁可以在进程间共享。 PTHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED
// 函数返回值为0表示成功,否则表示失败。
int pthread_spin_destroy(pthread_spinlock_t *lock);
上锁与解锁
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
POSIX 屏障(barrier)是一种线程同步机制,可以用于在多个线程中同步执行某些操作。屏障会阻止线程继续执行,直到所有线程都到达了屏障点,然后所有线程都可以继续执行。这对于需要多个线程协作完成某个任务的场景非常有用。
初始化与销毁
#include <pthread.h>
int pthread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr, unsigned count);
/*
参数说明:
barrier:指向要初始化的屏障的指针。
attr:指向屏障属性的指针。如果为NULL,则使用默认属性。
count:指定屏障的数目。当有count个线程到达屏障点时,它们将被释放。
函数返回值为0表示成功,否则表示失败。
*/
等待
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
样例
//10秒后二者才会输出内容,知道10秒后才有2个线程到达屏障点
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_barrier_t barrier;
void *thread_func1(void *arg)
{
(5);
sleep(&barrier);
pthread_barrier_wait("thread_func1\n");
printf(stdout);
fflushreturn NULL;
}
void *thread_func2(void *arg)
{
(10);
sleep(&barrier);
pthread_barrier_wait("thread_func2\n");
printf(stdout);
fflushreturn NULL;
}
int main()
{
pthread_t thread1, thread2;
(&barrier, NULL, 2);
pthread_barrier_init(&thread1, NULL, thread_func1, NULL);
pthread_create(&thread2, NULL, thread_func2, NULL);
pthread_create(3);
sleep(thread1, NULL);
pthread_join(thread2, NULL);
pthread_joinreturn 0;
}