线程概念
- 准确的定义是:线程是“一个进程内部的控制序列”
- 一切进程至少都有一个线程
- 线程是程序执行的最小单位(调度的最小单位)
Linux下线程的特点
1,线程共享进程数据,但也拥有自己的一部分数据
线程ID
一组寄存器(硬件上下文)
栈
errno
信号屏蔽字
调度优先级
2,一个进程的多个线程共享
同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
3,线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多,能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
4,线程的缺点
a)性能损失
- 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器
如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变
b)健壮性降低
- 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的
c)缺乏访问控制
- 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响(同时访问临界资源,造成混乱)
d)编程难度提高
- 编写与调试一个多线程程序比单线程程序困难得多
线程与进程的区别
- 进程是资源竞争的基本单位(承担资源分配的基本实体)
- 线程是程序执行的最小单位(调度的基本单位)
- 创建一个进程时,要创建PCB,分配资源,而创建一个线程时,只创建PCB,共享资源
- 线程是在进程内部运行的
- 线程的执行粒度更细
- 进程之间相互独立,而线程之间资源共享
- 线程的切换更容易
- 线程的释放成本更低
进程退出有三种情况
a)代码运行完,结果正确
b)代码运行完,结果不正确
c)异常终止(收到信号)而线程退出只有两种情况
a)代码运行完,结果正确
b)代码运行完,结果不正确因为线程异常退出就代表进程要异常退出
线程的创建/等待/终止
01.创建线程
与线程有关的函数构成了一个完整的系列
绝大多数函数的名字都是以pthread_
打头的
要使用这些函数库,要通过引入头文件#include<pthread.h>
链接这些线程函数库时要使用编译器命令的-lpthread
选项
//功能:创建一个新的线程
//原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
pthreads函数出错时不会设置全局变量errno,而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码
对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内errno变量的开销更小
在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL
在这种实现下,线程又被称为轻量级进程(Light Weighted Process)
每一个用户态的线程,在内核中都对应一个调度实体
也拥有自己的进程描述符(task_struct结构体)
没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID
但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程
每个线程作为一个独立的调度实体在内核态都有自己的进程描述符
进程和内核的描述符一下就变成了1 :N的关系
POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?
于是Linux内核引入了线程组的概念
struct task_struct
{
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;
...
struct list_head thread_group;
...
};
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应
进程描述符结构体中的pid,表面上对应的是进程ID,其实它对应的是线程ID
进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID
现在介绍的线程ID,不同于pthread_t 类型的线程ID,和进程ID一样,线程ID是pid_t类型的变量
而且是用来唯一标识线程的一个整型变量
如何查看一个线程的ID
ps -eLf |head -1 && ps -eLf |grep main |grep -v grep
ps命令中的-L选项,会显示如下信息:
- LWP: 线程ID,即gettid()系统调用的返回值
- NLWP: 线程组内线程的个数
可以看出上面main 进程是多线程的,进程ID为6101,线程ID分别为6101,6102
Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将该系统调用封装起来
如果确实需要获得线程ID,可以采用如下方法:
#include <sys/syscall.h>
pid_t tid;
tid = syscall(SYS_gettid);
从上面可以看出,main进程的ID为6101,下面有一个线程的ID也是6101,这不是巧合线程组内的第一个线程,在用户态被称为主线程(main thread)
在内核中被称为group leader
内核在创建第一个线程时,会将线程组的ID值设置成第一个线程的线程ID
group_leader指针则指向自身, 即主线程的进程描述符
所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程
至于线程组其他线程的ID则又内核负责分配
其线程组ID总是和主线程的线程组ID一致
无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样
进程有父子进程的概念,但在线程组里面,所有的线程都是平等的
线程ID及进程地址空间布局
pthread_ create
函数会产生一个线程ID,存放在第一个参数指向的地址中
该线程ID和前面说的线程ID不是一回事
前面讲的线程ID属于进程调度的范畴
因为线程是轻量级进程,是操作系统调度的最小单位,所以需要一个数值来唯一表示该线程
pthread_ create
函数产生并标记在第一个参数指向的地址中的线程ID中
属于NPTL线程库的范畴
线程库的后续操作,就是根据该线程ID来操作线程的
线程库NPTL提供了pthread_ self
函数,可以获得线程自身的ID:
pthread_t pthread_self(void);
pthread_t
到底是什么类型呢?取决于实现
对于Linux目前的NPTL实现而言
pthread_t
类型的线程ID,本质就是一个进程地址空间上的一个地址
02.等待线程
为什么需要线程等待?
因为已经退出的线程,其空间没有被释放,仍然在进程的地址空间内
创建新的线程不会复用刚才退出线程的地址空间
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,这个指针指向线程的返回值
返回值:成功返回0;失败返回错误码
调用该函数的线程将挂起等待,直到id为thread的线程终止
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的
总结如下
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED
- 如果thread线程是自己调用pthread_exit 终止的 ,value_ptr所指向的单元存放的是传给pthread_exit的参数
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
//线程等待
//线程1
void *thread1( void *arg )
{
printf("thread 1 returning...\n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
//线程2
void* thread2(void* arg)
{
printf("thread 2 returning...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
return (void*)p;
}
//线程3
void* thread3(void* arg)
{
while(1)
{
printf("thread 3 returning...\n");
sleep(1);
}
return NULL;
}
int main()
{
//主线程
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
void *ret;
//线程1
pthread_create(&tid1, NULL, thread1, NULL);
pthread_join(tid1, &ret);
printf("thread 1 return, thread id %lx, return code:%d\n", tid1, *(int *)ret);
free(ret);
//线程2
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid2, &ret);
printf("thread 2 return, thread id %lx, return code:%d\n", tid2, *(int *)ret);
free(ret);
//线程3
pthread_create(&tid3, NULL, thread3, NULL);
sleep(3);
//终止正在执行的线程
pthread_cancel(tid3);
pthread_join(tid3, &ret);
if ( ret == PTHREAD_CANCELED )
printf("thread 3 return, thread id %lx, return code:PTHREAD_CANCELED\n", tid3) ;
else
printf("thread return, thread id %lx, return code:NULL\n", tid3);
}
03.线程终止
如果需要只终⽌某个线程而不终止整个进程
有三种方法:
1,从线程函数return
这种方法对主线程不适用,从main函数return相当于调用exit
2,线程可以调用pthread_ exit终止自己
pthread_exit 函数
功能:线程终⽌
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意
pthread_exit
或者return
返回的指针所指向的内存单元必须是全局的或者是用malloc
分配的
不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了
3,一个线程可以调用pthread_ cancel
终止同一进程中的另一个线程
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
线程分离与结合属性
分离线程
默认情况下,新创建的线程是joinable的(默认必须被等待的)
线程退出后,需要对其进行pthread_join
操作
否则无法释放资源,从而造成内存泄漏
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统
当线程退出时,自动释放线程资源
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
pthread_join()
函数的替代函数
可回收
创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的
存储空间
该函数不会阻塞主线程
如果tid尚未终止,pthread_detach()不会终止该线程
参数:
tid:线程标识符
返回值:
pthread_detach()
调用成功返回0
其他任何返回值都表示出现了错误
如果检测到以下任一情况
pthread_detach()
将失败并返回相应的值
- EINVAL:tid是已分离线程
- ESRCH:tid不是当前进程中有效的未分离线程
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
pthread_detach(pthread_self()); //自己分离自己
joinable和分离是冲突的,一个线程不能既是joinable的又是分离的
线程的同步与互斥
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内
这种情况,变量归属单个线程,其他线程无法获得这种变量
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量
可以通过数据的共享,完成线程之间的交互
多个线程并发的操作共享变量,会带来一些问题
例如
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int tickets = 100;
void* ticket(void* arg)
{
char* id = (char*)arg;
while(1)
{
if(tickets > 0)
{
usleep(100000);
printf("%s sell tickets %d\n", id, tickets);
tickets--;
}
else
{
break;
}
}
return NULL;
}
int main()
{
pthread_t t1, t2, t3, t4;
//创建4个线程抢票
pthread_create(&t1, NULL, ticket, "thread 1");
pthread_create(&t2, NULL, ticket, "thread 2");
pthread_create(&t3, NULL, ticket, "thread 3");
pthread_create(&t4, NULL, ticket, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
并没有得到正确的结果, 因为
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- usleep 模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
- tickets - - 操作不是一个原子操作
而是对应三条汇编指令
- load:将共享变量tickets从内存加载到寄存器中
- update: 更新寄存器里面的值,执行-1操作
- store:将新值从寄存器写回共享变量tickets的内存地址
要解决以上问题,需要做到三点
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区
要做到这三点,本质上就是需要一把锁。Linux提供的这把锁叫互斥量
互斥量
初始化互斥量有两种方法
- 方法1,静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 方法2,动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr _t *restrict attr);
//参数
mutex:要初始化的互斥量
attr:NULL
销毁互斥量
- 使用
PTHREAD_ MUTEX_ INITIALIZER
初始化的互斥量不需要销毁 - 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁和解锁
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//返回值
成功返回0,失败返回错误号
调用pthread_ lock
时,可能会遇到以下情况
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞,等待互斥量解锁
改进刚才的抢票系统
//线程的同步与互斥问题
//售票系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int tickets = 100;
//定义一把互斥锁,并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* ticket(void* arg)
{
char* id = (char*)arg;
while(1)
{
//加锁
pthread_mutex_lock(&mutex);
if(tickets > 0)
{
usleep(100000);
printf("%s sell tickets %d\n", id, tickets);
tickets--;
//解锁
pthread_mutex_unlock(&mutex);
}
else
{
//解锁
pthread_mutex_unlock(&mutex);
break;
}
}
return NULL;
}
int main()
{
pthread_t t1, t2, t3, t4;
//初始化互斥锁
//pthread_mutex_init(&mutex,NULL);
//创建4个线程抢票
pthread_create(&t1, NULL, ticket, "thread 1");
pthread_create(&t2, NULL, ticket, "thread 2");
pthread_create(&t3, NULL, ticket, "thread 3");
pthread_create(&t4, NULL, ticket, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
现在就变得有序起来了, 不会产生混乱
条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
例如
- 一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中
这种情况就需要用到条件变量
初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *rest rict attr);
//参数
cond: 要初始化的条件变量
attr: NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mute x);
//参数
cond:要在这个条件变量上等待
mutex:互斥量
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒多个
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
//定义一个条件变量
pthread_cond_t cond;
//定义一个互斥量
pthread_mutex_t mutex;
void *r1( void *arg )
{
while ( 1 )
{
pthread_cond_wait(&cond, &mutex);
printf("thread1: 我醒了...\n");
}
}
void *r2(void *arg )
{
printf("thread2: 我每3s唤醒一次等待\n");
while ( 1 )
{
sleep(1);
printf("thread2: 1 ...\n");
sleep(1);
printf("thread2: 2 ...\n");
sleep(1);
printf("thread2: 3 ...\n");
//唤醒等待
pthread_cond_signal(&cond);
}
}
int main( void )
{
//定义两个线程id
pthread_t t1, t2;
//初始化条件变量和互斥量
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
//创建两个线程
pthread_create(&t1, NULL, r1, NULL);
pthread_create(&t2, NULL, r2, NULL);
//线程等待
pthread_join(t1, NULL);
pthread_join(t2, NULL);
//销毁互斥量和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
为什么pthread_ cond_ wait
需要互斥量?
条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足
所以必须要有一个线程通过某些操作,改变共享变量
使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
条件不会无缘无故变得满足, 必然会牵扯到共享数据的变化
所以一定要用互斥锁来保护。没有互斥锁就无法安全获取和修改共享数据
按照上面的说法,设计出如下的代码
先上锁,发现条件不满足,解锁,然后等待在条件变量上
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false)
{
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,
//信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
由于解锁和等待不是原子操作
调用解锁之后,pthread_ cond_ wait
之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_ cond_ wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_ cond_ wait
所以解锁和等待必须是一个原子操作
int pthread_ cond_ wait(pthread_ cond_ t *cond,
pthread_ mutex_ t * mutex);
进入该函数后,判断条件变量是否等于0
等于,就把互斥量变成1,直到cond_ wait返回,把条件变量改成1,把互斥量恢复成原样
条件变量使用规范
等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 1
struct msg
{
struct msg *next;
int num;
};
struct msg *head = NULL;
pthread_cond_t cond;
pthread_mutex_t mutex;
pthread_t threads[CONSUMERS_COUNT + PRODUCERS_COUNT];
void *consumer(void *p)
{
int num = *(int *)p;
free(p);
struct msg *mp;
for ( ; ; )
{
pthread_mutex_lock(&mutex);
while ( head == NULL )
{
printf("%d 开始等待...\n", num);
pthread_cond_wait(&cond, &mutex);
}
printf("%d 停止等待...\n", num);
printf("%d 开始消费...\n", num);
mp = head;
head = mp->next;
pthread_mutex_unlock(&mutex);
printf("消费了: %d\n", mp->num);
free(mp);
printf("%d 停止消费...\n", num);
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
int num = *(int *)p;
free(p);
for ( ; ; )
{
printf("%d 开始生产...\n", num);
mp = (struct msg *)malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1;
printf("生产了: %d\n", mp->num);
pthread_mutex_lock(&mutex);
mp->next = head;
head = mp;
printf("%d 停止生产...\n", num);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(rand() % 5);
}
}
int main( void )
{
srand(time(NULL));
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
int i;
for(i = 0; i < CONSUMERS_COUNT; i++)
{
int *p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&threads[i], NULL, consumer, (void *)p);
}
for(i = 0; i < PRODUCERS_COUNT; i++)
{
int *p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&threads[CONSUMERS_COUNT + i], NULL, producer, (void *)p);
}
for (i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
{
pthread_join(threads[i], NULL);
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
POSIX信号量
POSIX 信号量和 SystemV 信号量作用相同,都是用于同步操作,达到无冲突访问共享资源的目的。 但 POSIX 可以用于线程间同步
a) 初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
b) 销毁信号量
int sem_destroy(sem_t *sem);
c) 等待信号量
- 功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);
d) 发布信号量
- 功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);
上面生产者-消费者的例子是基于链表的,其空间可以动态分配,现在基于固定大小的队列重写这个程序
/*************************************************************************
# File Name: test.c
# Author: rjm
# mail: rjm96@foxmail.com
# Created Time: 2018年04月21日 星期六 09时44分24秒
************************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
//基于固定大小环形队列的生产者--消费者模型
//一个生产者, 一个消费者
#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 1
//队列大小为10
#define BUFFSIZE 10
//定义一个整形数组, 代表队列
int g_buffer[BUFFSIZE];
unsigned short in = 0;
unsigned short out = 0;
unsigned short produce_id = 0;
unsigned short consume_id = 0;
//定义两个全局的信号量
sem_t g_sem_full;
sem_t g_sem_empty;
//定义一把全局的互斥锁
pthread_mutex_t g_mutex;
//定义一个全局数组, 保存线程id
pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];
//消费者函数
void *consume(void *arg)
{
int i;
int num = *(int *)arg;
free(arg);
while (1)
{
printf("%2d 等待缓冲区不为空\n", num);
//等待信号量g_sem_empty, 将信号量的值 -1
sem_wait(&g_sem_empty);
//加锁
pthread_mutex_lock(&g_mutex);
for (i = 0; i < BUFFSIZE; i++)
{
printf("%2d ", i);
if (g_buffer[i] == -1)
printf("%s", "缓冲区为空");
else
printf("%2d", g_buffer[i]);
if (i == out)
printf("\t<--consume");
printf("\n");
}
consume_id = g_buffer[out];
printf("%2d 开始消费产品 %2d\n", num, consume_id);
g_buffer[out] = -1;
out = (out + 1) % BUFFSIZE;
printf("%2d 停止消费产品 %2d\n", num, consume_id);
//解锁
pthread_mutex_unlock(&g_mutex);
//发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
sem_post(&g_sem_full);
sleep(1);
}
return NULL;
}
//生产者函数
void *produce(void *arg)
{
int i;
int num = *(int *)arg;
free(arg);
while (1)
{
printf("%2d 等待缓冲区不为满\n", num);
//g_sem_full信号量值 -1
sem_wait(&g_sem_full);
//加锁
pthread_mutex_lock(&g_mutex);
for (i = 0; i < BUFFSIZE; i++)
{
printf("%2d ", i);
if (g_buffer[i] == -1)
printf("%s", "缓冲区为空");
else
printf("%2d", g_buffer[i]);
if (i == in)
printf("\t<--produce");
printf("\n");
}
g_buffer[in] = produce_id;
printf("%2d 开始生产产品 %2d\n", num, produce_id);
in = (in + 1) % BUFFSIZE;
printf("%2d 停止生产产品 %2d\n", num, produce_id++);
//解锁
pthread_mutex_unlock(&g_mutex);
//信号量值 +1
sem_post(&g_sem_empty);
sleep(2);
}
return NULL;
}
int main()
{
int i;
for (i = 0; i < BUFFSIZE; i++)
{
g_buffer[i] = -1;
}
sem_init(&g_sem_full, 0, BUFFSIZE);
sem_init(&g_sem_empty, 0, 0);
pthread_mutex_init(&g_mutex, NULL);
for (i = 0; i < CONSUMERS_COUNT; i++)
{
int *p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&g_thread[i], NULL, consume, (void *)p);
}
for (i = 0; i < PRODUCERS_COUNT; i++)
{
int *p = (int *)malloc(sizeof(int));
*p = i;
pthread_create(&g_thread[CONSUMERS_COUNT + i], NULL, produce, (void *)p);
}
for (i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
{
pthread_join(g_thread[i], NULL);
}
sem_destroy(&g_sem_full);
sem_destroy(&g_sem_empty);
pthread_mutex_destroy(&g_mutex);
return 0;
}