目录
2.1 线程创建函数pthread_create()原型和头文件:
2.2 线程创建函数pthread_create()用法案例:
2.3 线程退出函数pthread_exit()原型和头文件:
2.4 线程等待函数pthread_join()原型和头文件:
2.5 线程脱离函数pthread_detach()原型和头文件:
2.6 获取线程ID函数pthread_self()原型和头文件:
2.7 线程ID比较函数pthread_equal()原型和头文件:
3.1 创建互斥锁函数pthread_mutex_init()原型和头文件:
3.2 销毁互斥锁函数pthread_mutex_destroy()原型和头文件:
3.3 加锁pthread_mutex_lock()和解锁pthread_mutex_unlock()函数原型和头文件:
4.1 创建条件变量函数pthread_cond_init()原型和头文件:
4.2 销毁条件变量函数pthread_cond_destroy()原型和头文件:
4.3 条件变量等待函数pthread_cond_wait()原型和头文件(无条件等待):
4.4 条件变量等待函数pthread_cond_timedwait()原型和头文件(计时等待):
4.5 条件变量触发函数pthread_cond_signal和pthread_cond_broadcast原型和头文件:
一、线程基本概念
1.1 进程与线程的区别:
典型的UNIX/linux进程可以看成是只有一个控制线程,一个进程在同一时刻只做一件事情,有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
-
进程 进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
-
线程 线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
-
进程线程的区别 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,最好用线程。
1.2 使用线程的好处:
-
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
-
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
-
提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
-
使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
-
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
二、线程相关函数
-
多线程开发在linux平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。
-
线程操作有:创建,退出,等待。
-
互斥锁操作有:创建,销毁,加锁和解锁。
-
条件操作有 :创建,销毁,触发,广播和等待。
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
2.1 线程创建函数pthread_create()原型和头文件:
/*
Linux下 man pthread_create查看手册
*/
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
int 函数返回值,成功pthread_create()返回0;当出现错误时,它返回一个错误编号,并且*thread的内容是未定义的
tidp 指向线程标识符的指针
attr 用来设置线程属性,如果没有则填NULL。
start_rtn 表示线程运行函数的地址
arg 线程运行函数的参数,如果没有参数则填NULL
-
函数详解:
当 pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
2.2 线程创建函数pthread_create()用法案例:
#include <stdio.h>
#include <pthread.h>
void* func1(void *arg)
{
printf("th1:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取新线程的ID
printf("th1:data = %d\n",*((int *)arg));
}
int main()
{
int ret;
pthread_t th1; //线程标识符
int data = 100;
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th1, NULL, func1, &data); //创建了一个th1线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
printf("main:线程ID = %ld\n",(unsigned long)pthread_self()); //获取主线程的ID
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,NULL); //等待th1线程终止,但是不获取th1线程终止状态
return 0;
}
2.3 线程退出函数pthread_exit()原型和头文件:
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
-
线程只是从启动例程中返回,返回值是线程的退出码。
-
线程可以被同一进程中的其他线程取消。
-
线程调用pthread_exit。
/*
Linux下 man pthread_exit查看手册
*/
#include <pthread.h>
int pthread_exit(void *retval);
int 函数返回值,成功pthread_exit()返回0;当出现错误时,它返回一个错误编号
void *retval 表示线程退出状态,是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函 数访问到这个指针。
-
函数详解:
在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数是使一个线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行。 需要注意一点,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收。 如果在主线程中使用pthread_exit函数,会导致子线程还在,内存无法被回收,成为僵尸进程。引入pthread_join函数。
2.4 线程等待函数pthread_join()原型和头文件:
/*
Linux下 man pthread_join查看手册
*/
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
int 函数返回值,如果执行成功pthread_join返回0,当出现错误时,它返回一个错误编号
pthread_t thread 线程ID
void **retval 存储线程结束状态,整个指针和pthread_exit的参数是同一块内存地址
-
函数详解:
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
2.5 线程脱离函数pthread_detach()原型和头文件:
/*
Linux下 man pthread_detach查看手册
*/
#include <pthread.h>
int pthread_detach(pthread_t thread);
int 函数返回值,如果成功pthread_detach返回0,当出现错误时,它返回一个错误编号
pthread_t thread 需要脱离的线程(标识符)
-
函数详解:
一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。 当一个可汇合的线程终止时: 它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。 当一个脱离的线程终止时:像守护进程一样,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。 如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态。
2.6 获取线程ID函数pthread_self()原型和头文件:
/*
Linux下 man pthread_self查看手册
*/
#include <pthread.h>
pthread_t pthread_self(void);
pthread_t 函数返回值,返回调用线程的ID
-
函数详解:
对于线程ID比较,为了可移植操作,我们不能简单地把线程ID当作整数来处理,因为不同系统对线程ID的定义可能不一样。我们应该要用pthread_self函数获取一个线程的ID 此函数可与pthread_detach函数配合使用,想让自己脱离的线程使用,就如以下语句:
pthread_detach(pthread_self());
2.7 线程ID比较函数pthread_equal()原型和头文件:
/*
Linux下 man pthread_equal查看手册
*/
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
int 函数返回值,如果两个线程id相等,pthread_equal()返回一个非零值;否则返回0
pthread_t tid1 需要进行比较的第一个线程ID
pthread_t tid2 需要进行比较的第二个线程ID
2.8 子线程退出主线程获取子线程的退出状态(一):
#include <stdio.h>
#include <pthread.h>
void* func1(void *arg)
{
static int ret = 10; //必须使用static修饰,否则值会发生错误
printf("th1:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取新线程的ID
printf("th1:data = %d\n",*((int *)arg));
//int pthread_exit(void *retval);
pthread_exit((void *)&ret); //th1子线程退出返回子线程退出状态为整型数
}
int main()
{
int ret;
pthread_t th1; //线程标识符
int data = 100;
int *pret = NULL;
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th1, NULL, func1, &data); //创建了一个th1线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
printf("main:线程ID = %ld\n",(unsigned long)pthread_self()); //获取主线程的ID
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,(void **)&pret); //等待th1线程终止并获取th1线程终止状态
printf("main:th1线程退出状态*pret = %d\n",*pret);
return 0;
}
2.9 子线程退出主线程获取子线程的退出状态(二):
#include <stdio.h>
#include <pthread.h>
void* func1(void *arg)
{
static char *str = "th1 is run out"; //必须使用static修饰,否则值会发生错误
printf("th1:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取新线程的ID
printf("th1:data = %d\n",*((int *)arg));
//int pthread_exit(void *retval);
pthread_exit((void *)str); //th1子线程退出返回子线程退出状态为一个字符串
}
int main()
{
int ret;
pthread_t th1; //线程标识符
int data = 100;
char *pstr = NULL;
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th1, NULL, func1, &data); //创建了一个th1线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
printf("main:线程ID = %ld\n",(unsigned long)pthread_self()); //获取主线程的ID
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,(void **)&pstr); //等待th1线程终止并获取th1线程终止状态
printf("main:th1线程退出状态*pret = %s\n",pstr);
return 0;
}
2.10 传递给线程运行函数的参数为一个结构体:
#include <stdio.h>
#include <pthread.h>
struct Test
{
int data;
char str[32];
};
void* func1(void *arg)
{
static int ret = 10;
struct Test *p = (struct Test *)arg;
printf("th1:%ld为新创建的th1线程ID\n",(unsigned long)pthread_self()); //获取th1子线程ID
printf("th1:p->data = %d\n",p->data);
printf("th1:p->str = %s\n",p->str);
//int pthread_exit(void *retval);
pthread_exit((void *)&ret); //th1子线程退出返回子线程退出状态为一个整型数
}
int main()
{
int ret;
pthread_t th1; //线程标识符
struct Test t1 = {100,"chenlichen handsome"};
int *pret = NULL;
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
ret = pthread_create(&th1, NULL, func1, (void *)&t1); //创建了一个th1子线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
printf("main:主线程ID = %ld\n",(unsigned long)pthread_self); //获取主线程ID
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,(void **)&pret); //等待th1线程终止并获取th1线程终止状态
printf("main:th1子线程的退出状态*pret = %d\n",*pret);
return 0;
}
三、互斥锁相关函数
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。
互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。
3.1 创建互斥锁函数pthread_mutex_init()原型和头文件:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_mutex_t *restrict mutex 是指想要初始化的互斥锁的指针
const pthread_mutexattr_t *restrict attr 用于指定互斥锁属性,如果为NULL则使用默认属性(缺省属性)
-
互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
PTHREAD_MUTEX_TIMED_NP,缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3.2 销毁互斥锁函数pthread_mutex_destroy()原型和头文件:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_mutex_t *mutex 是指想要销毁的互斥锁指针
3.3 加锁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); //解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //测试加锁
int 函数返回值,若成功返回0,否则返回错误编号
pthread_mutex_t *mutex 想要操作的互斥锁指针
-
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
3.4 线程与互斥锁应用实例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int num = 0; //定义全局变量为共有资源
pthread_mutex_t mutex; //定义互斥量
void* func1(void *arg)
{
printf("th1:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取th1新线程的ID
printf("th1:data = %d\n",*((int *)arg));
pthread_mutex_lock(&mutex); //加锁
while(1){
printf("th1:num = %d\n",num++);
sleep(1);
if(num == 3){
pthread_mutex_unlock(&mutex); //解锁
printf("th1 线程退出======================\n");
pthread_exit(NULL); //th1线程退出
}
}
}
void* func2(void *arg)
{
printf("th2:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取th2新线程的ID
printf("th2:data = %d\n",*((int *)arg));
while(1){
printf("th2:num = %d\n",num);
pthread_mutex_lock(&mutex); //加锁
num++;
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
pthread_exit(NULL); //th2线程退出
}
int main()
{
int ret;
pthread_t th1; //th1线程标识符
pthread_t th2; //th2线程标识符
int data = 100;
//int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
ret = pthread_mutex_init(&mutex,NULL); //创建互斥锁
if(ret != 0){
printf("创建互斥锁失败\n");
}
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th1, NULL, func1, &data); //创建了一个th1线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th2, NULL, func2, &data); //创建了一个th2线程
if(ret == 0){
printf("main:创建th2线程成功\n");
}
printf("main:线程ID = %ld\n",(unsigned long)pthread_self()); //获取主线程的ID
while(1){
printf("main:num = %d\n",num);
sleep(1);
}
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,NULL); //等待th1线程终止,但是不获取th1线程终止状态
pthread_join(th2,NULL); //等待th2线程终止,但是不获取th2线程终止状态
//int pthread_mutex_destroy(pthread_mutex_t *mutex);
ret = pthread_mutex_destroy(&mutex); //销毁互斥锁
if(ret != 0){
printf("销毁互斥锁失败\n");
}
return 0;
}
线程1先夺得互斥锁,直到线程1满足退出条件,解锁后,线程2的主要代码部分才开始运行。
四、条件变量相关函数
条件变量:本身不是锁,但可以造成线程阻塞,通常与互斥锁配合使用; 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待 "条件变量的条件成立 "而挂起;另一个线程使 "条件成立 "(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
4.1 创建条件变量函数pthread_cond_init()原型和头文件:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_cond_t *restrict cond 想要初始化的条件变量的指针
pthread_condattr_t *restrict attr 用于指定互斥锁属性,如果为NULL则使用默认属性(缺省属性)
4.2 销毁条件变量函数pthread_cond_destroy()原型和头文件:
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_cond_t *cond 想要销毁的条件变量的指针
4.3 条件变量等待函数pthread_cond_wait()原型和头文件(无条件等待):
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_cond_t *restrict cond 条件变量指针
pthread_mutex_t *restrict mutex 互斥锁指针
pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait
返回时,互斥量再次被锁住。
4.4 条件变量等待函数pthread_cond_timedwait()原型和头文件(计时等待):
#include <pthread.h>
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
struct timespec *restrict abstime);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_cond_t *restrict cond 条件变量指针
pthread_mutex_t *restrict mutex 互斥锁指针
struct timespec *restrict abstime 是一个绝对时间,Linux中常用的时间结构有struct timespec 和 struct timeval
#include <sys/time.h> //需要包含的头文件
struct timespec
{
time_t tv_sec; /* Seconds. */
long tv_nsec; /* Nanoseconds. */
};
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
/*这里面的超时时间是一个绝对值,也就是距离1970-1-1 日的时间值,而不是一个时间段。比如说当前时间为2024-6-8 14:43:00.100,我们想通过这个函数设置最大超时为2500ms,那么就需要设置abstime时间为2024-6-8 14:43:02.600.*/
/*abstime参数设置实例*/
struct timespec abstime;
struct timeval now;
gettimeofday(&now, NULL);
abstime.tv_sec = now.tv_sec + 5;
abstime.tv_nsec = now.tv_usec * 1000;
pthread_cond_timedwait
函数的工作方式与pthread_cond_wait
函数类似,只是多了一个abstime。abstime指定了等待的时间,它是通过timespec
结构指定
4.5 条件变量触发函数pthread_cond_signal和pthread_cond_broadcast原型和头文件:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int 函数返回值,若成功返回0,失败返回错误编号
pthread_cond_t *cond 条件变量指针
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
4.6 条件变量与互斥量结合案例:
-
当线程2操作全局变量num为3时,线程1执行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex; //互斥锁
pthread_cond_t cond; //条件变量
int num = 0; //共享变量
void* func1(void *arg)
{
printf("th1:线程ID = %ld\n",(unsigned long)pthread_self()); //获取th1线程ID
printf("th1:参数 = %d\n",*((int *)arg)); //打印参数
while(1){
pthread_cond_wait(&cond, &mutex); //条件变量等待
printf("th1:num = %d\n",num); //打印共享变量
printf("th1 run=============================\n");
num = 0; //重置共享变量
sleep(1); //睡眠1秒
}
pthread_exit(NULL); //线程退出
}
void* func2(void *arg)
{
printf("th2:线程ID = %ld\n",(unsigned long)pthread_self()); //获取th2线程ID
printf("th2:参数 = %d\n",*((int *)arg)); //打印参数
while(1){
printf("th2:num = %d\n",num); //打印共享变量
pthread_mutex_lock(&mutex); //加锁
num++; //加1
if(num == 3){
pthread_cond_signal(&cond); //条件变量通知
}
pthread_mutex_unlock(&mutex); //解锁
sleep(1); //睡眠1秒
}
pthread_exit(NULL); //线程退出
}
int main()
{
int ret;
int data = 100;
pthread_t th1; //th1线程标识符
pthread_t th2; //th2线程标识符
//int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
ret = pthread_mutex_init(&mutex, NULL); //互斥锁初始化
if(ret != 0){
printf("main:mutex初始化失败\n");
}
//int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
ret = pthread_cond_init(&cond, NULL); //条件变量初始化
if(ret != 0){
printf("main:条件变量初始化失败\n");
}
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
ret = pthread_create(&th1, NULL, func1, (void *)&data); //创建th1线程
if(ret == 0){
printf("main:th1线程创建成功\n");
}
ret = pthread_create(&th2, NULL, func2, (void *)&data); //创建th2线程
if(ret == 0){
printf("main:th2线程创建成功\n");
}
pthread_join(th1,NULL); //等待th1线程退出
pthread_join(th2,NULL); //等待th2线程退出
//int pthread_mutex_destroy(pthread_mutex_t *mutex);
ret = pthread_mutex_destroy(&mutex); //互斥锁销毁
if(ret != 0){
printf("main:mutex销毁失败\n");
}
//int pthread_cond_destroy(pthread_cond_t *cond);
ret = pthread_cond_destroy(&cond); //条件变量销毁
if(ret != 0){
printf("main:条件变量销毁失败\n");
}
return 0;
}
4.7 静态初始化互斥锁和条件变量:
-
对于互斥锁与条件变量的初始化,也可以用静态初始化即使用宏,如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER //互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER //条件变量
这部分代码是在C语言中使用pthread库来创建互斥锁和条件变量的实例。互斥锁用于保护共享资源,以确保在同一时间只有一个线程可以访问它。条件变量用于在线程之间的同步,允许一个线程在满足特定条件之前等待,或者当条件发生变化时通知其他线程。
代码中使用了PTHREAD_MUTEX_INITIALIZER和PTHREAD_COND_INITIALIZER宏,它们分别用于静态地初始化互斥锁和条件变量,确保它们被正确地设置为默认值。
总的来说,这部分代码的主要功能是初始化了一个互斥锁和一个条件变量,以便后续在多线程中使用。