【pthread】POSIX接口简述
pthread简介
POSIX Threads简称Pthreads,此标准定义了一套C语言的类型,函数和常量。定义在pthread.h头文件和一个线程库里,大约有100多个API,可以分为4大类:
- 线程管理:包括线程创建(create),线程分离(detach),线程连接(join)及设置和查询线程属性的函数等
- 互斥锁:用于限制线程对共享数据的访问,保护共享数据的完整性。包括创建、销毁、锁定和解锁互斥锁及一些用于设置或修改互斥量属性等函数。
- 条件变量:用于共享一个互斥量的线程间的通信。包括条件变量的创建、销毁、等待和发送信号等函数
- 读写锁和屏障:包括读写锁和屏障的创建、销毁、等待及相关属性设置等函数
函数前缀 | 函数组 |
---|---|
pthread_ | 线程属性对象 |
pthread_mutex_ | 互斥锁 |
pthread_mutexaddr_ | 互斥锁属性对象 |
pthread_cond_ | 条件变量 |
pthread_condattr_ | 条件变量属性对象 |
pthread_rwlock_ | 读写锁 |
pthread_rwlockattr_ | 读写锁属性对象 |
pthread_spin_ | 自旋锁 |
pthread_barrier_ | 屏障 |
pthread_barrierattr_ | 屏障属性对象 |
sem_ | 信号量 |
mq_ | 消息队列 |
pthread API - 线程
创建新的线程
函数原型
int pthread_create(pthread_t *restrict tid,const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),void *restrict arg);
参数说明
tid:指向想成句柄的指针,不能为NULL
attr:指向线程属性的指针,如果使用NULL,则使用默认的线程属性
start:线程入口函数地址
arg: 传递给线程入口函数的参数
线程脱离
调用此函数,如果pthread线程没有结束,则将thread线程属性的分离状态设置为detached;当thread线程已经结束时,系统将回收pthread线程占用的资源。
函数原型
int pthread_detach (pthread_t thread);
参数说明
thread : 线程句柄
使用方法
子线程调用pthread_detach(pthread_self()),或者其他线程调用pthread_detach(thread_id)。注意:线程属性的分离状态设置为detached,此线程不能被pthread_join函数等待,或者重新被设置为detached。
等待线程结束
在pthread的实现中,每个线程都有两个特性:joinable和detached,当创建一个线程的默认属性是joinable,表示线程是可以使用pthread_join进行同步的。
当一个线程调用pthread_join(T,ret),这个函数返回的时候表示线程T已经终止了,执行完成。那么就可以释放与线程T的相关的系统资源。
如果一个线程的状态是detached状态的话,当线程结束的时候与这个线程相关的资源会被自动释放掉,将资源归还给系统,也就不需要其他的线程调用pthread_join来释放线程的资源了。
此函数会调用该函数的线程以阻塞的方式等待线程分离属性为joinable的thread线程运行结束,并获得thread线程的返回值,返回值的地址保存在value_ptr里,并释放thread线程占用的资源。
函数原型
int pthread_join(pthread_t thread,void **value_ptr);
参数说明
thread:线程句柄(线程描述符)
value_ptr : 用户定义的指针,用来存储被等待线程的返回值地址,可由函数pthread_join()获取
返回值
EDEADLK:表示检测到四艘,比如两个线程都调用pthread_join函数等待对方执行
EINVAL:线程不是一个joinable的线程,比如:pthread_join一个detached线程
ESRCH:thread是一个无效线程,比如:没有create线程
0:函数调用成功
线程标识
进程ID是用pid_t表示的,而线程的ID使用pthread_t数据类型来表示的
对两个线程ID进行比较
函数原型
#include <pthread.h>
int pthread_equal(pthread_t tidl,pthread_t tid2);
参数说明
tid1:线程1句柄
tid2:线程2句柄
获取自身的线程ID
函数原型
pthread_t pthread_self(void);
线程退出
pthread线程调用此函数会终止执行,如同进程调用exit()函数一样,并返回一个指向线程返回值的指针。线程退出由线程自身发起。
如果在进程进程中任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止
单个线程可以通过3种方式退出,即在不终止整个进程的情况下,停止它的控制流。
(1)线程可以简单地从启动例程中返回,返回值是线程的退出码
(2) 例程可以被同一进程中的其他线程取消
(3) 线程调用pthread_exit
函数原型
#include <pthread.h>
void pthread_exit(void *rval_ptr);
参数说明
rval_ptr: 用户定义的指针,用来存储被等待线程的返回值地址,可由函数pthread_join()获取
pthread API - 互斥锁
互斥锁初始化
函数原型
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
参数说明
mutex:互斥锁句柄,不能为NULL
attr:指向互斥锁属性的指针,若指针为NULL,则使用默认的属性
返回值
0:初始化成功
EINVAL:参数无效
销毁互斥锁
函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数说明
mutex:互斥锁句柄,不能为NULL
返回值
0:销毁成功
EINVAL:mutex为空或mutex已销毁
EBUSY:互斥锁正在被使用
注意
当确定互斥锁没有被锁住,且没有线程阻塞在该互斥锁上,才可以销毁该互斥锁。
阻塞方式上锁
函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数说明
mutex:互斥锁句柄,不能为NULL
返回值
0:成功上锁
EINVAL:参数无效
EDEADLK:线程重复调用
非阻塞方式上锁
函数原型
int pthread_mutex_trylock(pthead_mutex_t *mutex);
参数说明
mutex:互斥锁句柄,不能为NULL
返回值
0:成功上锁
EINVAL:参数无效
EDEADLK:线程重复调用
EBUSY:mutex已经被其他线程上锁返回EBUSY
注意
此函数与阻塞方式上锁的区别在于,如果互斥锁mutex已经被上锁,线程不会被阻塞,而是返回错误码
互斥锁解锁
函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数说明
mutex:互斥锁句柄,不能为NULL
返回值
0:成功上锁
EINVAL:参数无效
EPERM:
pthread API - 条件变量
条件变量其实就是一个信号量,用于线程间同步。条件变量用于阻塞一个线程,当条件满足时向阻塞的线程发送一个条件,阻塞线程就被唤醒,条件变量需要和互斥锁配合使用,互斥锁用于保护共享数据。
条件变量的主要操作包括:
调用pthread_cond_init()对条件变量初始化
调用pthread_cond_destory()销毁一个条件变量
调用pthread_cond_wait()等待一个条件变量
调用pthread_cond_signal()发送一个条件变量。
初始化条件变量
此函数会初始化cond条件变量,并根据attr指向的条件变量属性设置其属性,attr一般设置NULL使用默认值
函数原型
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
参数说明
cond:条件变量句柄,不能为NULL
attr:指向条件变量属性的指针,若为NULL则使用默认属性值
返回值
0:初始化成功
EINVAL:参数无效
销毁条件变量
销毁后cond处于未初始化状态。销毁之后条件变量的属性及控制块参数将不在有效,可以调用pthread_cond_init()或静态方式重新初始化,销毁条件变量前需要确定没有线程被阻塞在该条件变量上,也不会等待获取、发信号或者广播
函数原型
int pthread_cond_destroy(pthread_cond_t *cond);
参数说明
cond:条件变量句柄,不能为NULL
返回值
0:销毁成功
EINVAL:参数无效
EBUSY:条件变量正在使用
阻塞方式获取条件变量
此函数会以阻塞方式获取cond条件变量。线程等待条件变量前需要现将mutex互斥锁锁住,此函数首先判断条件变量是否可用,如果不可用则初始化一个条件变量,之后解锁mutex互斥锁,然后尝试获取一个信号量,当信号量值大于零时,表明信号量可用,线程将获得信号量, 也就获得该条件变量,相应的信号量值会减1。如果信号量的值等于零,表明信号量不可用,线程将阻塞直到信号量可用,之后将对mutex互斥锁再次上锁。
函数原型
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
参数说明
cond:条件变量句柄,不能为NULL
mutex:指向互斥锁控制块指针
返回值
0:成功获取条件变量
EINVAL:参数无效
指定阻塞时间获取条件变量
此函数和pthread_cond_wait()函数唯一的差别在于,如果条件变量不可用,线程将被阻塞abstime时长,超时后函数将直接返回ETIMEDOUT错误码,线程将会被唤醒进入就绪态。
函数原型
int pthread_cond_timewait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
参数说明
cond :条件变量句柄,不能为NULL
mutex : 指向互斥锁控制块的指针,不能NULL
abstime : 指定的等待事件
返回值
0:成功获取条件变量
EINVAL:参数无效
ETIMEDOUT:超时返回
发送满足条件信号量
此函数会发送一个信号且只唤醒一个等待cond条件变量的线程,就是发送一个信号量。当信号量的值等于零,并且有线程等待这个信号量时,将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量。否则将把信号量的值加1。
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
参数说明
cond :条件变量句柄,不能为NULL
返回值
0:总是成功
广播
调用此函数将唤醒所有等待cond条件变量的线程。
函数原型
int pthread_cond_broadcast(pthread_cond_t *cond);
参数说明
cond :条件变量句柄,不能为NULL
返回值
0:广播成功
EINVAL:参数无效
pthread API - 消息队列
消息队列可以接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被环形以接收并处理消息。
消息队列主要操作包括:
通过函数mq_open()创建或者打开
调用mq_send()发送一个消息到消息队列
调用mq_receive()从消息队列获取一条信息
调用mq_unlink()删除消息队列
创建或打开消息队列
根据消息队列的名字name创建一个新的消息队列或者打开一个已经存在的消息队列。 Oflag的可选值有0、O_CREAT或O_CREAT|O_EXCL。如果Oflag设置为O_CREAT则会创建一个新的消息队列。如果Oflag设置O_CREAT|O_EXCL,如果消息队列已经存在则会返回NULL,如果不存在则会创建一个新的消息队列。如果Oflag设置为0,消息队列不存在则会返回NULL。
函数原型
mqh_t mq_open(const char *name,int oflag,...);
参数说明
name : 消息队列
oflag: 消息队列打开方式
返回值
成功则返回消息队列句柄,否则返回NULL
分离消息队列
根据消息队列名称name查找消息队列,若找到,则将消息队列置为分离状态,之后若持有计数为0,则删除消息队列,并释放消息队列占有的资源。
函数原型
int mq_unlink(const char *name);
参数说明
name : 消息队列名称
返回值
成功返回0,若消息 队列不存在则返回-1
关闭消息队列
当一个线程终止时,会对其占用的消息队列执行此关闭操作。不论线程是自愿终止还是非自愿终止都会执行这个关闭操作,相当于是消息队列的持有计数减1,若减1后持有计数为0,且消息队列处于分离状态,则会删除mqdes消息队列并释放其占有的资源。
函数原型
int mq_close(mqd_t mqdes);
参数说明
mqdes:消息队列句柄
返回值
成功返回0,否则返回-1
阻塞方式发送消息
向mqdes消息队列发送一条消息,此函数把msg_ptr指向的消息添加到mqdes消息队列中,发送的消息长度msg_len必须小于或者等于创建消息队列时设置的最大消息长度,插入到 msg_prio的指定位置。
如果消息队列已经满,即消息队列中的消息数量等于最大消息数,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。
函数原型
int mq_send(mqd_t mqdes,
const char *msg_ptr,
size_t msg_len,
unsigned msg_prio);
参数说明
mqdes:消息队列句柄
sg_ptr:指向要发送的消息的指针,不能为NULL
msg_len:发送的消息的长度
msg_prio:消息优先级,如果不需要设置优先级,可以设置为0
返回值
成功返回0,否则返回-1
阻塞方式接收消息
把mqdes消息队列里面最老的消息移除消息队列,并把消息放到msg_ptr指向的内存里。如果消息队列为空,调用mq_receive()函数的线程将会阻塞,直到消息队列中消息可用。
函数原型
ssize_t mq_receive(mqd_t mqdes,
char *msg_ptr,
size_t msg_len,
unsigned *msg_prio,
const struct timespec (abs_timeout);
参数说明
mqdes:消息队列句柄
msg_ptr:指向要发送的消息的指针,不能为NULL
msg_len:发送的消息的长度
msg_prio:消息优先级,如果不需要设置优先级,可以设置为0
abs_timeout:指定的等待时间
返回值
成功则返回消息长度,否则返回-1
指定阻塞时间接收消息
和mq_receive()函数的区别在于,若消息队列为空,线程将阻塞abs_timeout时长,超时后函数直接返回-1,线程将被唤醒由阻塞态进入就绪态。
函数原型
ssize_t mq_timedreceive(mqd_t mqdes,
char *msg_ptr,
size_t msg_len,
unsigned *msg_prio,
const struct timespec *abs_timeout);
参数说明
mqdes:消息队列句柄,不能为NULL
msg_ptr:指向要发送的消息指针,不能为NULL
msg_len:发送的消息的长度
msg_prio:消息优先级,如果不需要设置优先级,可以设置为0
abs_timeout:指定的等待时间
返回值
成功则返回消息长度,否则返回-1
pthread API - 屏障
屏障是多线程同步的一种方法。把先后到达的多个线程同时阻拦住,直到所有线程到期,然后撤去阻拦同时放行。先到达的线程将会阻塞,等到所有调用pthread_barrie_wait()函数的线程(屏障初始化时会指定count)都到达后,这些线程就会由阻塞状态进入就绪状态再次参与系统调度。
屏障是基于条件变量和互斥锁实现的。主要操作包括:调用pthread_barrier_init()初始化一个屏障,其他线程调用pthread_barrier_wait(),所有线程到期后线程唤醒进入准备状态,屏障不在使用调用pthread_barrier_destroy()销毁一个屏障。
屏蔽控制块
创建一个屏障前需要先定义一个pthread_barrier_t 屏障控制块,定义在pthread.h的头文件中。
struct pthread_barrier
{
int count; /*指定的等待线程个数*/
pthread_cond_t cond; /* 条件变量 */
pthread_mutex_t mutex; /* 互斥锁 */
};
typedef struct pthread_barrier pthread_barrier_t;
创建屏障
创建一个barrier屏障,并根据默认的参数对屏障控制块的条件变量和互斥锁初始化,初始化后指定的等待线程个数为count个,必须对应count个线程 调用pthread_barrier_wait()。attr一般设置NULL使用默认值即可
函数原型
int pthread_barrier_init(pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr,
unsigned count);
参数说明
attr:指向屏蔽属性的指针,传入NULL,则使用默认值,非NULL必须使用 PTHREAD_PROCESS_PRIVATE
barrier:屏蔽句柄
count:指定的等待线程的个数
函数返回
初始化成功返回0,参数无效返回EINVAL。
销毁屏障
此函数会销毁一个barrier屏障。销毁之后屏障的属性及控制块参数将不在有效,但可以调用pthread_barrier_init()重新初始化。
函数原型
int pthread_barrier_destroy(pthread_barrier_t *barrier);
参数说明
barrier: 屏障句柄
函数返回
销毁成功返回0,参数无效返回EINVAL。
等待屏障
此函数同步等待在barrier前的线程,由每个线程主动调用,若屏障等待线程个数count不为0,count将减1,若减1后count为0,表明所有线程都已经到达栏杆前,所有到达的线程将被唤醒重新进入就绪状态,参与系统调度。若减一后count不为0,表明还有线程没有到达屏障,调用的线程将阻塞直到所有线程到达屏障。
函数原型
int pthread_barrier_wait(pthread_barrier_t *barrier);
参数说明
barrier:屏蔽句柄
返回值
等待成功返回0,参数无效返回EINVAL,死锁返回EDEADLK。
pthread API - 信号量
信号量可以用于进程与进程之前间,或者进程内线程之间的通信。每个信号量都有一个不会小于0的信号量值,对应信号量的可用数量。调用sem_init()或者sem_open()给信号量值赋初值,调用sem_post()函数可以让信号量值加1,调用sem_wait可以让信号量值减1,如果当信号量为0,调用sem_wait()的线程被挂起在该信号量的等待队列上,知道信号量值大于0,处于可用状态。
POSIX信号量分为有名信号量和无名信号量:
- 有名信号量:其值保存在文件中,一般用于进程间同步或互斥
- 无名信号量:其值保存在内存中,一把用于线程间同步或互斥
无名信号量
无名信号量的值保存在内存中,一般用于线程间同步会互斥。在使用之前,必须先调用sem_init()初始化。
无名信号量初始化
此函数初始化一个无名信号量sem,根据给定的或默认的参数对信号量相关数据结构进行初始化,并把信号量放入信号量链表里。初始化后信号量值为给定的初始化value.
函数原型
int sem_init(sem_t *sem,int pshared,unsigned int value);
参数说明
sem:信号量句柄
value:信号量初始值
pshared:不为0时此信号量在进程间通向,否则只能为当前进程的所有线程共享
函数返回值
初始化成功返回0,否则返回-1
销毁无名信号量
此函数会销毁一个无名信号量sem,并释放信号量占用的资源
函数原型
int sem_destroy(sem_t *sem);
参数说明
sem:信号量句柄
函数返回值
销毁成功返回0,否则返回-1
有名信号量
有名信号量的值保存在文件中,一般用于进程间同步或互斥。两个进程可以操作相同名称的有名信号量。
创建或打开有名信号量
此函数会根据信号量名字name创建一个新的信号量或者打开一个已经存在的信号量
函数原型
sem_t *sem_open(const char *name,int oflag,...);
参数说明
name:信号量名称
oflag:信号量的打开方式,O_CREAT:创建一个新的信号量,O_CREAT|O_EXCL:如果信号量已经存在则返回NULL,如果不存在则创建一个新的信号量。
函数返回值
成功则返回信号量句柄,否则返回NULL。
分离有名信号量
此函数会根据信号量名称查找该信号量,该信号量存在,则将该信号量标记为分离状态。之后检查引用计数,若为0,则立即删除信号量,若值不为0,则得到所有持有该信号量的线程关闭信号量之后才会删除。
函数原型
int sem_unlink(const char *name);
参数说明
name : 信号量名称
函数返回
分离成功返回0,若信号量不存在则返回-1。
关闭有名信号量
当一个线程终止时,会对其占用的信号量执行此关闭操作。不会线程时资源终止还是非自愿终止都会执行这个关闭操作,相当于是信号量的持有计数-1.若减一后持有计数为0且信号量已经处于分离状态,则会删除sem新海量并释放其占有的资源。
函数原型
int sem_close(sem_t *sem);
参数说明
sem:信号量句柄
函数返回
成功关闭返回0,否则返回-1。
获取信号量值
此函数可以获取sem信号量的值,并保存在sval指向的内存里,可以知道信号量的资源数量
函数原型
int sem_getvalue(sem_t *sem,int *sval);
参数说明
sem:信号量句柄,不能为NULL
sval:保存获取的信号量值地址,不能为NULL
函数返回
成功关闭返回0,否则返回-1。
阻塞方式等待信号量
线程调用此函数获取信号量,若信号量值大于零,表明信号量可用,线程获得信号量,信号量值减1。若信号量值等于0,表明信号量不可用,线程阻塞进入挂起状态,并按照先进先出的方式排队等待,直到信号量可用。
函数原型
int sem_wait(sem_t *sem);
参数说明
sem:信号量句柄,不能为NULL
函数返回
成功返回0,否则返回-1。
非阻塞方式获取信号量
此函数是sem_wait()函数的非阻塞版,是rt_sem_take(sem,0)函数的封装。当信号量不可用时,线程不会阻塞,而是直接返回。
函数原型
int sem_trywait(sem_t *sem);
参数说明
sem:信号量句柄,不能为NULL
函数返回
成功返回0,否则返回-1。
指定阻塞时间等待信号量
此函数和sem_wait()函数的区别在于,若信号量不可用,线程将阻塞abs_timeout时长,超时后函数返回-1,线程将被唤醒由阻塞态进入就绪态。
函数原型
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参数说明
sem:信号量句柄
abs_timeout:指定的等待时间,单位是操作系统时钟节拍(OS Tick)
函数返回
成功返回0,否则返回-1。
发送信号量
此函数将释放一个sem信号量,若等待该信号量的线程队列不为空,表明有线程在等待该信号量,第一个等待该信号量的线程将由挂起状态切换到就绪状态,等待系统调度,若没有线程等待该信号量,该信号量值将加1
函数原型
int sem_post(sem_t *sem);
参数说明
sem:信号量句柄,不能为NULL
函数返回
成功返回0,否则返回-1。
实例代码
实例为生产者消费者模型。一个生产者线程和一个消费者线程对同一块内存进行操作,,生产者往共享内存填充数据,消费者从共享内存读取数据。
此程序会创建2个线程,2个信号量,一个信号量表示共享数据为空状态,一个信号量表示共享数据不为空状态,一个互斥锁用于保护共享资源。生产者线程生产好数据后会给消费者发送一个full_sem信号量,通知消费者线程有数据可用,休眠2秒后会等待消费者线程发送的empty_sem信号量。消费者线程等到生产者发送的full_sem后会处理共享数据,处理完后会给生产者线程发送empty_sem信号量。程序会这样一直循环。
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
/* 静态方式初始化一个互斥锁用于保护共享资源*/
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/* 2个信号量控制块,一个表示资源空信号,一个表示资源满信号 */
static sem_t empty_sem,full_sem;
/* 指向线程控制块的指针 */
static pthread_t tid1;
static pthread_t tid2;
/* 函数返回值检查 */
static void check_result(char* str,int result)
{
if (0 == result)
{
printf("%s successfully!\n",str);
}
else
{
printf("%s failed! error code is %d\n",str,result);
}
}
/* 生产者生产的结构体数据,存放在链表里 */
struct node
{
int n_number;
struct node* n_next;
};
struct node* head = NULL; /* 链表头,是共享资源 */
/* 消费者线程入口函数 */
static void* consumer(void* parameter)
{
struct node* p_node = NULL;
while (1)
{
sem_wait(&full_sem);
pthread_mutex_lock(&mutex); /* 对互斥锁上锁, */
while (head != NULL) /* 判断链表里是否有元素 */
{
p_node = head; /* 拿到资源 */
head = head->n_next; /* 头指针指向下一个资源 */
/* 打印输出 */
printf("consume %d\n",p_node->n_number);
free(p_node); /* 拿到资源后释放节点占用的内存 */
}
pthread_mutex_unlock(&mutex); /* 临界区数据操作完毕,释放互斥锁 */
sem_post(&empty_sem); /* 发送一个空信号量给生产者 */
}
}
/* 生产者线程入口函数 */
static void* product(void* patameter)
{
int count = 0;
struct node *p_node;
while(1)
{
/* 动态分配一块结构体内存 */
p_node = (struct node*)malloc(sizeof(struct node));
if (p_node != NULL)
{
p_node->n_number = count++;
pthread_mutex_lock(&mutex); /* 需要操作head这个临界资源,先加锁 */
p_node->n_next = head;
head = p_node; /* 往链表头插入数据 */
pthread_mutex_unlock(&mutex); /* 解锁 */
printf("produce %d\n",p_node->n_number);
sem_post(&full_sem); /* 发送一个满信号量给消费者 */
}
else
{
printf("product malloc node failed!\n");
break;
}
sleep(2); /* 休眠2秒 */
sem_wait(&empty_sem); /* 等待消费者发送空信号量 */
}
}
int main()
{
int result;
sem_init(&empty_sem,NULL,0);
sem_init(&full_sem,NULL,0);
/* 创建生产者线程,属性为默认值,入口函数是product,入口函数参数为NULL*/
result = pthread_create(&tid1,NULL,product,NULL);
check_result("product thread created ",result);
/* 创建消费者线程,属性为默认值,入口函数是consumer,入口函数参数是NULL */
result = pthread_create(&tid2,NULL,consumer,NULL);
check_result("consumer thread created ",result);
return 0;
}
参考文章:
UNIX环境高级编程 - 线程
https://www.bookstack.cn/read/rtthread-manual-doc/16.2.md