1 线程的概念
进程是程序的一次执行过程也是系统进行资源分配和调度的基本单位,而线程是进程中实施资源调度和分派的基本单位。也就是说一个进程中至少有一个线程(即使不额外使用线程,进程内部也有一个执行线程)。
同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack,我们称为线程栈),自己的寄存器环境(register context)、自己的线程本地存储(thread-local storage)。
- 单线程: 同一个时刻,只允许执行一个线程
- 多线程: 同一个时刻,可以执行多个线程
并发
: 同一个时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单的说,单核cpu实现的多任务就是并发。并行
: 同一个时刻,多个任务同时执行。多核cpu可以实现并行
应用场合:
- 需要让用户感觉在
同时
做多件事情时,比如,处理文档的进程,一个线程处理用户编辑,一个线程同时统计用户的字数。 - 当一个应用程序,需要
同时
处理输入、计算、输出时,可开3个线程,分别处理输入、计算、输出。让用户感觉不到等待。 - 高并发编程。
-
2 线程ID
- 进程 ID 在整个系统 中是唯一的,但线程 ID 不同,线程 ID 只有在它所属的进程上下文中才有意义。
- 进程 ID 使用 pid_t 数据类型来表示,它是一个非负整数。而线程 ID 使用 pthread_t 数据类型来表示
#include <pthread.h>
// 一个线程可通过库函数 pthread_self()来获取自己的线程 ID
// 该函数调用总是成功,返回当前线程的线程 ID。
pthread_t pthread_self(void);
// 使用 pthread_equal()函数来检查两个线程 ID 是否相等
// 两个线程相等,则 pthread_equal()返回一个非零值;否则返回 0。
int pthread_equal(pthread_t t1, pthread_t t2);
3 创建线程-pthread_create()
启动程序时,创建的进程只是一个单线程的进程,称之为初始线程或主线程。
主线程可以使用库函数 pthread_create()负责创建一个新的线程,创建出来的新线程被称为主线程的子线 程,其函数原型如下所示:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函数参数和返回值含义如下:
- thread:pthread_t 类型指针,当 pthread_create()成功返回时,新创建的线程的线程 ID 会保存在参数 thread 所指向的内存中,后续的线程相关函数会使用该标识来引用此线程。
- attr:pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区,pthread_attr_t 数据类型定义了线程的各种属性,为 NULL 时表示使用默认属性。
- start_routine:参数 start_routine 是一个函数指针,指向一个函数,新创建的线程从 start_routine()函数开始运行,该函数返回值类型为void *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create() 函数的第四个参数 arg。如果需要向 start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为 arg 参数传入。
- arg:传递给 start_routine()函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。
- 返回值:成功返回 0;失败时将返回一个错误号,并且参数 thread 指向的内容是不确定的。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
return (void *)0;
}
int main(void)
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL); //创建线程
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
sleep(1);
exit(0);
}
3.1 创建线程时--线程属性
调用 pthread_create()创建线程时,参数 attr 如果不使用默认值,则参数 attr 必须要指向一个 pthread_attr_t 对象;在 Linux 下,使用 pthread_attr_t 数据类型定义线程的所有属性。
3.2 线程属性-初始化及销毁
- 当定义 pthread_attr_t 对象之 后 ,需要 使用 pthread_attr_init()函数对该对象进行初始化操作
- 当对象 不再使 用时, 需要使用 pthread_attr_destroy()函数将其销毁
- 函数原型如下所示:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
- 参数 attr :指向一个 pthread_attr_t 对象,即需要进行初始化的线程属性对象。
- 返回值:调用成功时返回 0,失败将返回一个非 0 值的错误码。
3.3 线程属性--分离状态属性
在创建线程时就确定要将该线程分离,可以修改 pthread_attr_t 结构中的 detachstate 线程属性, 让线程一开始运行就处于分离状态。
- 调用函数 pthread_attr_setdetachstate()设置 detachstate 线程属性,
- 调用 pthread_attr_getdetachstate()获取 detachstate 线程属性
- 其函数原型如下所示:
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
参数 attr :指向 pthread_attr_t 对象
参数 detachstate 取值如下:
- PTHREAD_CREATE_DETACHED:新建线程一开始运行便处于分离状态,以分离状态启动线程, 无法被其它线程调用 pthread_join()回收,线程结束后由操作系统收回其所占用的资源;
- PTHREAD_CREATE_JOINABLE:这是 detachstate 线程属性的默认值,正常启动线程,可以被 其它线程获取终止状态信息。
以分离状态启动线程的示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
puts("Hello World!");
return (void *)0;
}
int main(int argc, char *argv[])
{
pthread_attr_t attr;
pthread_t tid;
int ret;
/* 对 attr 对象进行初始化 */
pthread_attr_init(&attr);
/* 设置以分离状态启动线程 */
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* 创建新线程 */
ret = pthread_create(&tid, &attr, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
exit(0);
}
3.4 线程栈-属性
每个线程都有自己的栈空间,pthread_attr_t 数据结构中定义了栈的起始地址以及栈大小。
- 调用函数 pthread_attr_getstack()可以获取这些信息,
- 函数 pthread_attr_setstack()对栈起始地址和栈大小进行设置,
- 其函 数原型如下所示:
#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
1. pthread_attr_setstack 的参数及返回值:
- attr:参数 attr 指向线程属性对象。
- stackaddr:设置栈起始地址为指定值。
- stacksize:设置栈大小为指定值;
- 返回值:成功返回 0,失败将返回一个非 0 值的错误码。
2. pthread_attr_getstack 的参数及返回值:
- attr:参数 attr 指向线程属性对象。
- stackaddr:调用 pthread_attr_getstack()可获取栈起始地址,并将起始地址信息保存在*stackaddr 中;
- stacksize:调用 pthread_attr_getstack()可获取栈大小,并将栈大小信息保存在参数 stacksize 所指向的内存中;
- 返回值:成功返回 0,失败将返回一个非 0 值的错误码。
如果想单独获取或设置栈大小、栈起始地址,可以使用下面这些函数:
#include <pthread.h>
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
4 终止线程
终止线程运行的方式:
- 线程的 start 函数执行 return 语句并返回指定值,返回值就是线程的退出码;
- 线程调用 pthread_exit()函数;
- 调用 pthread_cancel()取消线程
如果进程中的任意线程调用 exit()、_exit()或者_Exit(),那么将会导致整个进程终止。
4.1 thread_exit()函数
函数原型如下所示:
#include <pthread.h>
void pthread_exit(void *retval);
- 参数 retval 的数据类型为 void *,指定了线程的返回值、也就是线程的退出码,该返回值可由另一个线 程通过调用 pthread_join()来获取。
- 同理,如果线程是在 start 函数中执行 return 语句终止,那么 return 的返回值也是可以通过 pthread_join()来获取的。
如果主线程调用了 pthread_exit(),那么主线程也会终止, 但其它线程依然正常运行,直到进程中的所有线程终止才会使得进程终止。
5 回收线程-pthread_join()
在父、子进程当中,父进程可通过 wait()函数(或其变体 waitpid())阻塞等待子进程退出并获取其终止状态,回收子进程资源;而在线程当中,也需要如此,通过调用 pthread_join()函数来阻塞等待线程的终止, 并获取线程的退出码,回收线程资源;
pthread_join()函数原型如下所示:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
函数参数和返回值含义如下:
thread:pthread_join()等待指定线程的终止,通过参数 thread(线程 ID)指定需要等待的线程;
retval:
- 如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态(即目标线程通过 pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)复制到*retval 所指 向的内存区域;
- 如果目标线程被 pthread_cancel()取消,则将PTHREAD_CANCELED 放在*retval 中。
- 如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL。
返回值:成功返回 0;失败将返回错误码。
6 取消线程
有时候,在程序设计需求当中,需要向一个线程发送一个请求,要求它立刻退出,我们把这种操作称为取消线程,也就是向指定的线程发送一个请求,要求其立刻终止、退出。
但是,线程可以设置自己不被取消或者控制如何被取消,所以 pthread_cancel()并不会等待线程终止,仅仅只是提出请求。
6.1 请求取消一个线程-pthread_cancel
过调用 pthread_cancel()库函数向一个指定的线程发送取消请求,其函数原型如下所示:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数 thread 指定需要取消的目标线程;成功返回 0,失败将返 回错误码。
6.2 取消状态以及类型
默认情况下,线程是响应其它线程发送过来的取消请求的,响应请求然后退出线程。
线程可以选择不被取消或者控制如何被取消,通过 pthread_setcancelstate()和 pthread_setcanceltype()来设置线程的取消性状态和类型。
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
5.2.1 pthread_setcancelstate 的参数:
state :必须是以下值之一:
- PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
- PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求 挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE。
oldstate:将线程之前的取消性状态保存在参数 oldstate 指向的缓冲区中,设置为 NULL则不保存。
返回值:pthread_setcancelstate()调用成功将返回 0,失败返 回非 0 值的错误码。
5.2.2 pthread_setcanceltype 的参数:
type :必须是以下值之一:
- PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直 到线程到达某个取消点为止,这是所有新建线程包括 主线程默认的取消性类型。
- PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点(也许是立即取消,但不一定) 取消线程。
返回值:pthread_setcanceltype()函数调用成功将返回 0,失败返回非 0 值 的错误码。
6.3 取消点及函数
将线程的取消性类型设置为 PTHREAD_CANCEL_DEFERRED 时(线程可以取消状态下),收到其 它线程发送过来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用。
所谓取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请 求,这些函数就是取消点;在没有出现取消点时,取消请求是无法得到处理的。
取消点函数,只能是系统中存在的函数,不能是其他函数,通过命令"man 7 pthreads" 查询。
7 分离线程
默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。
可以调用 pthread_detach()将指定线程进行分离,也就是分离线程:
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 参数 thread: 指定需要分离的线程;thread为时pthread_self(),将线程自己分离。
- 返回值:成功将返回 0;失败将返回一个错误码。
- 处于分离状态的线程,当其终止后,能够自动回收线程资源。
- 一旦线程处于分离状态,就不能再使用 pthread_join()来获取其终止状态,此过程是不可逆的,一旦处于分离状态之后便不能再恢复到之前的状态。
8 线程同步
线程同步是为了对共享资源的访问进行保护,共享资源指的是多个线程都会进行访问的资源, 譬如定义了一个全局变量 a,线程 1 访问了变量 a、同样在线程 2 中也访问了变量 a,那么此时变量 a 就是多个线程间的共享资源,大家都要访问它。线程同步技术,用于实现同一时间只允许一个线程访问该变量,防止出现并发访问的情况、消除数据不一致的问题。
- 如果每个线程访问的变量都是其它线程不会读取和修改的(譬如线程函数内定义的局部变 量或者只有一个线程访问的全局变量),那么就不存在数据一致性的问题;
- 同样,如果变量是只读的,多个 线程同时读取该变量也不会有数据一致性的问题;
- 但是,当一个线程可以修改的变量,其它的线程也可以读 取或者修改的时候,这个时候就存在数据一致性的问题,需要对这些线程进行同步操作,确保它们在访问变 量的存储内容时不会访问到无效的值。
8.1 互斥锁
互斥锁(mutex)又叫互斥量,从本质上说是一把锁,在访问共享资源之前对互斥锁进行上锁,在访问 完成后释放互斥锁(解锁);对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻 塞,直到当前线程释放互斥锁。
互斥锁使用 pthread_mutex_t 数据类型表示,在使用互斥锁之前,必须首先对它进行初始化操作,可以使用两种方式对互斥锁进行初始化操作。
8.1.1 互斥锁初始化
1. 使用 PTHREAD_MUTEX_INITIALIZER 宏初始化互斥锁
互斥锁使 用 pthread_mutex_t 数 据类型表示,使用 PTHREAD_MUTEX_INITIALIZER 宏初始化,只适用于在定义的时候就直接进行初始化,互斥锁的操作如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2. 使用 pthread_mutex_init()函数初始化互斥锁
使用 PTHREAD_MUTEX_INITIALIZER 宏只适用于在定义的时候就直接进行初始化,对于其它情况则不能使用这种方式,譬如先定义互斥锁,后再进行初始化,或者在堆中动态分配的互斥锁,譬如使用 malloc() 函数申请分配的互斥锁对象,那么在这些情况下,可以使用 pthread_mutex_init()函数对互斥锁进行初始化, 其函数原型如下所示:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
函数参数和返回值含义如下:
- mutex:参数 mutex 是一个 pthread_mutex_t 类型指针,指向需要进行初始化操作的互斥锁对象;
- attr:参数 attr 是一个 pthread_mutexattr_t 类型指针,指向一个 pthread_mutexattr_t 类型对象,该对象用于定义互斥锁的属性,若将参数 attr 设置为 NULL,则表示将互斥锁的属性设置为 默认值,在这种情况下其实就等价于 PTHREAD_MUTEX_INITIALIZER 这种方式初始化,而不同之处在于, 使用宏不进行错误检查。
- 返回值:成功返回 0;失败将返回一个非 0 的错误码。
使用 pthread_mutex_init()函数对互斥锁进行初始化示例:
pthread_mutex_t mutex; // 定义mutex
pthread_mutex_init(&mutex, NULL); // 初始化锁
或者:
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);
互斥锁的属性(attr)
如果不使用默认属性,在调用 pthread_mutex_init()函数时,参数 attr 必须要指向一个 pthread_mutexattr_t 对象,而不能使用 NULL。
当定义 pthread_mutexattr_t 对象之后,需要使用 pthread_mutexattr_init()函数对该对象进行初始化操作,当对象不再使用时,需要使用 pthread_mutexattr_destroy()将其销毁,函数原型如下所 示:
#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); // 销毁属性对象
int pthread_mutexattr_init(pthread_mutexattr_t *attr); // 初始化属性对象
参数 attr :指向需要进行初始化的 pthread_mutexattr_t 对象,
返回值:调用成功返回 0,失败将返回非 0 值的错误码。
互斥锁的类型属性控制着互斥锁的锁定特性,一共有 4 中类型:
- PTHREAD_MUTEX_NORMAL:一种标准的互斥锁类型,不做任何的错误检查或死锁检测。
- PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。
- PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;
- PTHREAD_MUTEX_DEFAULT : 此 类 互 斥 锁 提 供 默 认 的 行 为 和 特 性 。使 用 宏 PTHREAD_MUTEX_INITIALIZER 初 始 化 的 互 斥 锁 , 或 者 调 用 参 数 arg 为 NULL 的 pthread_mutexattr_init()函数所创建的互斥锁,都属于此类型。
使用 pthread_mutexattr_gettype()函数得到互斥锁的类型属性,使用 pthread_mutexattr_settype()修改 /设置互斥锁类型属性,其函数原型如下所示:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); // 获取锁属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); // 设置锁属性
pthread_mutexattr_gettype()函数:
- 参数 attr :指向 pthread_mutexattr_t 类型对象;
- 返回值:调用成功会将互斥锁类型属性保存在参数 type 所指向的内存中,通过它返回出来;
pthread_mutexattr_settype()函数:
- 参数 attr :指向 pthread_mutexattr_t 类型对象;
- 返回值:参数 attr 指向的 pthread_mutexattr_t 对象的类型属性设置为参数 type 指定的类型。
pthread_mutex_t mutex; // 创建锁对象
pthread_mutexattr_t attr; // 销毁属性对象
/* 初始化互斥锁属性对象 */
pthread_mutexattr_init(&attr);
/* 将类型属性设置为 PTHREAD_MUTEX_NORMAL */
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
/* 初始化互斥锁 */
pthread_mutex_init(&mutex, &attr);
......
/* 使用完之后 */
pthread_mutexattr_destroy(&attr); // 销毁属性对象
pthread_mutex_destroy(&mutex); // 销毁锁对象
8.1.2 互斥锁加锁和解锁
互斥锁初始化之后,处于一个未锁定状态,
1. 调用 pthread_mutex_lock()函数会被阻塞,直到互斥锁解锁
调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁,而调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁。其函数原型如下所示:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 参数 mutex :指向互斥锁对象;
- 返回值:调用成功时返回 0;失败将返回一个非 0 值的错误码。
2. 使用 pthread_mutex_trylock()以不阻塞的方式尝试对互斥锁进行加锁
- 如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,
- 如果 互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。
其函数原型如下所示:
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数 mutex :指向目标互斥锁,
返回值:成功返回 0,失败返回一个非 0 值的错误码,如果目标互斥锁已经被其它线程锁住,则调用失败返回 EBUSY。
8.1.3 销毁互斥锁
当不再需要互斥锁时,应该将其销毁,通过调用 pthread_mutex_destroy()函数来销毁互斥锁,其函数原型如下所示:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 参数 mutex :指向目标互斥锁;
- 返回值:调用成功情况下返回 0, 失败返回一个非 0 值的错误码。
特点:
- 不能销毁还没有解锁的互斥锁,否则将会出现错误;
- 没有初始化的互斥锁也不能销毁。
- 被 pthread_mutex_destroy()销毁之后的互斥锁,就不能再对它进行上锁和解锁了,需要再次调用 pthread_mutex_init()对互斥锁进行初始化之后才能使用。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_mutex_t mutex; // 定义锁
static int g_count = 0; // 定义全局变量(共享资源)
/* 线程函数 */
static void *new_thread_start(void *arg)
{
int loops = *((int *)arg);
int l_count, j;
for (j = 0; j < loops; j++) {
pthread_mutex_lock(&mutex); //互斥锁上锁
l_count = g_count;
l_count++;
g_count = l_count;
pthread_mutex_unlock(&mutex);//互斥锁解锁
}
return (void *)0;
}
static int loops;
int main(int argc, char *argv[])
{
pthread_t tid1, tid2;
int ret;
/* 获取用户传递的参数 */
if (2 > argc)
loops = 10000000; //没有传递参数默认为 1000 万次
else
loops = atoi(argv[1]);
/* 初始化互斥锁 */
pthread_mutex_init(&mutex, NULL);
/* 创建 2 个新线程 */
ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
/* 等待线程结束 */
ret = pthread_join(tid1, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid2, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
/* 打印结果 */
printf("g_count = %d\n", g_count);
/* 销毁互斥锁 */
pthread_mutex_destroy(&mutex);
exit(0);
}
8.2 互斥锁死锁
如果一个线程试图对同一个互斥锁加锁两次,该线程会陷入死锁 状态,一直被阻塞永远出不来。当超过 一个线程对同一组互斥锁(两个或两个以上的互斥锁)进行加锁时,就有可能发生死锁
解决方法:
- 定义互斥锁的层级关系,当多个线程对一组互斥锁操作时,总是应该按照相同的顺序对该组互斥锁进行锁定。
- 使用 pthread_mutex_trylock()以不阻塞的方式尝试对互斥锁进行加锁。
8.3 条件变量
互斥锁与条件变量详解_互斥锁和条件变量-CSDN博客
pthread_cond_wait()用法分析_pthread_cond_wait cpu 100%-CSDN博客
8.4 自旋锁
Linux线程同步(5)——互斥锁or自旋锁?_linux 互斥锁和自旋锁区别-CSDN博客