Linux多线程编程

一、线程概述

cpp 复制代码
线程概念:
进程:有独立的进程地址空间。有独立的pcb。    分配资源的最小单位
线程:有独立的pcb。没有独立的进程地址空间。  cpu执行的最小单位。
ps -Lf 进程id:可以查看该进程的全部线程
cpp 复制代码
1.轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
2.从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的。
3.进程可以蜕变成线程。
4.线程可看做寄存器和栈的集合
cpp 复制代码
线程共享资源:
1.文件描述符表(可用于通信)
2.每种信号的处理方式(哪个线程先抢到信号就优先接收)
3.当前工作目录。
4.用户ID和组ID。
5.内存地址空间(.text/.data/.bss/heap/共享库)(共享全局变量)

线程非共享资源:
1.线程id
2.处理器现场和栈指针(内核栈)。
3.独立的栈空间(用户空间栈)。
4.errno变量
5.信号屏蔽字。
6.调度优先级

优点:1.提高程序并发性 2.开销小 3.数据通信、共享数据方便
缺点:1.库函数,不稳定 2.调试、编写困难、gdb不支持 3.对信号支持不好。

二、创建线程

cpp 复制代码
pthread_t pthread_self(void);
获取线程id。线程id是在进程地址空间内部,用来标识线程身份的id号。
返回值:本线程id

int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
    void *(*start_rountn)(void *), void *arg);
参1:传出参数,表示新创建的子线程id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
参4:参3的参数。没有的话,传NULL
返回值:成功:0    失败:errno
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {	//子线程
	printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, char* argv[]) {
	pthread_t tid;

	//主线程
	printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());	//l-long u-unsigned

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		perror("pthread_create error");
	}

	sleep(1);	//防止进程先结束,导致子线程没有地址空间

	return 0;
}

三、循环创建多个子线程

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {
	int i = (int)arg;

	sleep(i);	//确保有序
	printf("---I'm %dth thread: pid = %d, tid = %lu\n", i+1, getpid(), pthread_self());
	
	return NULL;
}

int main(int argc, char* argv[]) {
	int i;
	int ret;
	pthread_t tid;

	for (i = 0; i < 5; i++) {
		ret = pthread_create(&tid, NULL, tfn, (void*)i);    //传参要用值传递,不能地址
		if (ret != 0) {
			sys_err("pthread_create error");
		}
	}

	sleep(i);
	printf("main: I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());

	return 0;
}

四、线程间全局变量共享

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int var = 100;

void* tfn(void* arg) {
	var = 200;
	printf("thread, var = %d\n", var);
	
	return NULL;
}

int main(int argc, char* argv[]) {
	printf("At first var = %d\n", var);
	
	pthread_t tid;
	pthread_create(&tid, NULL, tfn, NULL);
	sleep(1);

	printf("After pthread_create, var = %d\n", var);

	return 0;
}

//主子线程共享全局变量

五、pthread_exit退出线程

cpp 复制代码
void pthread_exit(void* retval):退出当前线程。
retval:退出值。无退出值时,NULL
exit0:退出当前进程。
return:返回到调用者那里去。
pthread_exitO:退出当前线程。

如果在主线程return,就会返回到执行的总程序,从而退出整个进程
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {
	int i = (int)arg;

	sleep(i);	//确保有序
	if (i == 2) {		// 第三个线程退出
		//exit(0);		// 表示退出进程,把整个大程序都退出了
		//return NULL;	// 表示返回到函数调用者那里去,可以退出线程
		pthread_exit(NULL);
	}

	printf("---I'm %dth thread: pid = %d, tid = %lu\n", i + 1, getpid(), pthread_self());

	return NULL;
}

int main(int argc, char* argv[]) {
	int i;
	int ret;
	pthread_t tid;

	for (i = 0; i < 5; i++) {
		ret = pthread_create(&tid, NULL, tfn, (void*)i);    //传参要用值传递,不能地址
		if (ret != 0) {
			sys_err("pthread_create error");
		}
	}

	sleep(i);
	printf("main: I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());

	return 0;
}
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {	//子线程
	printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, char* argv[]) {
	pthread_t tid;

	//主线程
	printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());	//l-long u-unsigned

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		perror("pthread_create error");
	}

	//sleep(1);	//防止进程先结束,导致子线程没有地址空间
    
	//return 0;
    
    pthread_exit((void*) 0);    //不退出总进程,退出主线程
}

六、pthread_join回收线程

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

struct thrd {
	int var;
	char str[256];
};

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {
	struct thrd *tval;    //必须用指针,不能是局部变量,因为在这个函数外就不存在了
	tval = malloc(sizeof(tval));
	
	tval->var = 100;
	strcpy(tval->str, "hello thread");

	return (void*)tval;
}

int main(int argc, char* argv[]) {
	pthread_t tid;
	struct thrd* retval;

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		sys_err("pthread_create error");
	}

	// 回收线程,retval是传出参数,把tval内容拿到手
	ret = pthread_join(tid, (void**)&retval);
	if (ret != 0) {
		sys_err("pthread_join error");
	}

	printf("child thread exit with var = %d, str = %s\n", retval->var, retval->str);

	pthread_exit(NULL);
}
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {
	return (void*)74;
}

int main(int argc, char* argv[]) {
	pthread_t tid;
	int* retval;

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		sys_err("pthread_create error");
	}

	// 回收线程,retval是传出参数,把tval内容拿到手
	ret = pthread_join(tid, (void**)&retval);
	if (ret != 0) {
		sys_err("pthread_join error");
	}

	printf("child thread exit with %d\n", (void*)retval);

	pthread_exit(NULL);
}
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

struct thrd {
	int var;
	char str[256];
};

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

void* tfn(void* arg) {
	struct thrd* tval = (struct thrd*)arg;

	tval->var = 100;
	strcpy(tval->str, "hello thread");

	return (void*)tval;
}

int main(int argc, char* argv[]) {
	pthread_t tid;
	struct thrd arg;
	struct thrd *retval;

	int ret = pthread_create(&tid, NULL, tfn, (void*)&arg);
	if (ret != 0) {
		sys_err("pthread_create error");
	}

	ret = pthread_join(tid, (void**)&retval);
	if (ret != 0) {
		sys_err("pthread_join error");
	}

	printf("child thread exit with var = %d, str = %s\n", retval->var, retval->str);

	pthread_exit(NULL);
}

七、pthread_cancel终止线程

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void* tfn(void* arg) {	//子线程
	while (1) {
		printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
		sleep(1);
	}

	return NULL;
}

int main(int argc, char* argv[]) {
	pthread_t tid;

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
		exit(1);
	}

	printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

	sleep(5);
	
	ret = pthread_cancel(tid);
	if (ret != 0) {
		fprintf(stderr, "pthread_cancel error:%s\n", strerror(ret));
		exit(1);
	}

	while (1);

	pthread_exit((void*)0);    //不退出总进程,退出主线程
}

//5秒后子线程被杀死
cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>

void* tfn1(void* arg) {
	printf("thread 1 returning\n");

	return (void*)111;		// exit(111);
}

void* tfn2(void* arg) {
	printf("thread 2 exiting\n");
	pthread_exit((void*)222);
}

void* tfn3(void* arg) {
	while (1) {
		printf("thread 3: I'm going to die in 3 seconds ...\n");
		sleep(1);

		//pthread_testcancel();		// 自己添加取消点
	}

	return (void*)666;
}

int main(int argc, char* argv[]) {
	pthread_t tid;
	void* tret = NULL;

	pthread_create(&tid, NULL, tfn1, NULL);
	pthread_join(tid, &tret);
	printf("thread 1 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn2, NULL);
	pthread_join(tid, &tret);
	printf("thread 2 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn3, NULL);
	sleep(3);
	pthread_cancel(tid);
	pthread_join(tid, &tret);
	printf("thread 3 exit code = %d\n\n", (int)tret);

	return 0;
}

//-1表示非正常退出(被杀死)
//成功被pthread_cancel杀死的线程,返回-1 使用pthread_join回收。
cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>

void* tfn1(void* arg) {
	printf("thread 1 returning\n");

	return (void*)111;		// exit(111);
}

void* tfn2(void* arg) {
	printf("thread 2 exiting\n");
	pthread_exit((void*)222);
}

void* tfn3(void* arg) {
	while (1) {
		//printf("thread 3: I'm going to die in 3 seconds ...\n");
		//sleep(1);

		pthread_testcancel();		// 自己添加取消点
	}

	return (void*)666;
}

int main(int argc, char* argv[]) {
	pthread_t tid;
	void* tret = NULL;

	pthread_create(&tid, NULL, tfn1, NULL);
	pthread_join(tid, &tret);
	printf("thread 1 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn2, NULL);
	pthread_join(tid, &tret);
	printf("thread 2 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn3, NULL);
	sleep(3);
	pthread_cancel(tid);	// 需要进内核的契机
	pthread_join(tid, &tret);
	printf("thread 3 exit code = %d\n\n", (int)tret);

	return 0;
}

//tfn3里面如果没有系统调用,就无法杀死线程
//if,for都无法进内核

八、pthread_detach线程分离

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void* tfn(void* arg) {
	printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, int argv[]) {
	pthread_t tid;

	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0) {
		fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_detach(tid);    //设置线程分离线程终止,会自动清理pcb,无需回收
	if (ret != 0) {
		fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
		exit(1);
	}

	sleep(1);

	ret = pthread_join(tid, NULL);
	if (ret != 0) {
		fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		exit(1);
	}

	printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

	pthread_exit((void*)0);
}

//线程分离之后结束,自己回收掉相关信息,导致pthread_join无法回收(tid),从而出错

九、线程属性设置分离线程

cpp 复制代码
线程属性:
设置分离属性。
pthread_attr_t attr;    创建一个线程属性结构体变量
pthread_attr_init(&attr);    初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);   设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL);    借助修改后的设置线程属性创建为分离态的新线程
pthread_attr_destroy(&attr);    销毀线程属性
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void* tfn(void* arg) {
	printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

	return NULL;
}

int main(int argc, int argv[]) {
	pthread_t tid;
	pthread_attr_t attr;

	int ret = pthread_attr_init(&attr);
	if (ret != 0) {
		fprintf(stderr, "attr_init error: %s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);	// 设置线程属性为分离属性
	if (ret != 0) {
		fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
		exit(1);
	}

	ret = pthread_create(&tid, &attr, tfn, NULL);
	if (ret != 0) {
		perror("pthread_create error");
	}

	ret = pthread_attr_destroy(&attr);
	if (ret != 0) {
		fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
		exit(1);
	}
    
    //pthread_join是阻塞回收
	ret = pthread_join(tid, NULL);    // 验证是否分离
	if (ret != 0) {
		fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
		exit(1);
	}

	printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

	pthread_exit((void*)0);
}

十、线程使用注意事项

cpp 复制代码
1.主线程退出其他线程不退出,主线程应调用pthread_exit。

2.避免僵尸线程。
pthreadjoin
pthread_detach
pthread_create指定分离属性。
被join的线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值

3.malloc和mmap申请的内存可以被其他线程释放(堆区共享)

4.应避免在多线程模型中调用 fork,子进程中只有调用fork的线程存在,其他线程在子进程
中均 pthread_exit,(进程中的一个线程fork,fork出来的进程中只有调用fork的线程存在)

5.信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。

十一、线程同步

cpp 复制代码
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。
同时其它线程为保证数据一致性,不能调用该功能。

线程同步:
协同步调,对公共区域数据按序访问。

放置数据混乱,产生与时间有关的错误。

数据混乱原因:
1.资源共享(独享资源则不会)
2.调度随机(意味着数据访问会出现竞争)
3.线程间缺乏必要的同步机制。

解决:使多个线程在访问共享资源的时候,出现互斥。

十二、mutex互斥锁

cpp 复制代码
Linux中提供一把互斥锁mutex(也称之为互斥量)。
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
锁本身不具备强制性。
资源还是共享的,线程间也还是竞争的。

但通过"锁"就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_mutex_t mutex;		// 定义一个互斥锁

void* tfn(void* arg) {
	srand(time(NULL));

	while (1) {
		phread_mutex_lock(&mutex);		// 加锁
		printf("hello ");
		sleep(rand() % 3);		// 模拟共享资源
		printf("world\n");
		pthread_mutex_unlock(&mutex);	// 解锁

		sleep(rand() % 3);
	}

	return NULL;
}

int main(void) {
	pthread_t tid;
	srand(time(NULL));

	int ret = pthread_mutex_init(&mutex, NULL);		// 初始化互斥锁
	if (ret != 0) {
		fprintf(stderr, "mutex init error:%s\n", strerror(ret));
		exit(1);
	}

	pthread_create(&tid, NULL, tfn, NULL);

	while (1) {
		pthread_mutex_lock(&mutex);
		printf("HELLO ");
		sleep(rand() % 3);
		printf("WORLD\n");
		phread_mutex_unlock(&mutex);
		sleep(rand() % 3);
	}

	pthread_join(tid, NULL);

	pthread_mutex_destroy(&mutex);		// 销毁互斥锁

	return 0;
}

// 用锁机制实现顺序打印大小写hello world
cpp 复制代码
如果sleep放在解锁之前就会出现以下情况:
因此访问完共享数据要立刻解锁
cpp 复制代码
注意事项:
尽量保证锁的粒度,越小越好。(访问共享数据前,加锁。访问结束立即解锁。)
互斥锁,初值为1。(pthread_mutex_init()函数调用成功。)
加锁:--操作(P操作),阻塞线程。
结束:++操作(V操作),唤醒阻塞的线程。

try锁:尝试加锁,成功--。失败则返回,设置错误号EBUSY

十三、读写锁

cpp 复制代码
读写锁特性:
1.读写锁是"写模式加锁"时,解锁前,所有对该锁加锁的线程都会被阻塞。

2.读写锁是"读模式加锁"时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。

3.读写锁是"读模式加锁"时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。
那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行(同时)阻塞,写锁优先级高

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;
当它以写模式锁住时,它是以独占模式锁住的。

写独占、读共享。

读写锁非常适合于对线程读的次数远大于写的情况。

锁只有一把。以读方式给数据加锁一一读锁。以写方式给数据加锁一一写锁。
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int counter;
pthread_rwlock_t rwlock;		// 全局的读写锁

// 3个线程不定时写同一全局资源,5个线程不定时读同一全局资源
void* th_write(void* arg) {
	int t;
	int i = (int)arg;

	while (1) {
		pthread_rwlock_wrlock(&rwlock);
		t = counter;
		usleep(1000);
		printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
		pthread_rwlock_unlock(&rwlock);
		usleep(10000);		// 给读锁让出CPU
	}

	return NULL;
}

void* th_read(void* arg){
	int i = (int)arg;

	while (1) {
		pthread_rwlock_rdlock(&rwlock);
		printf("------------read %d: %lu: %d\n", i, pthread_self(), counter);
		pthread_rwlock_unlock(&rwlock);
		usleep(2000);
	}

	return NULL;
}

int main(void) {
	int i;
	pthread_t tid[8];

	pthread_rwlock_init(&rwlock, NULL);

	for (i = 0; i < 3; i++) {
		pthread_create(&tid[i], NULL, th_write, (void*)i);
	}
		
	for (i = 0; i < 5; i++) {
		pthread_create(&tid[i + 3], NULL, th_read, (void*)i);
	}
		
	for (i = 0; i < 8; i++) {
		pthread_join(tid[i], NULL);
	}
		
	pthread_rwlock_destroy(&rwlock);

	return 0;
}

// 该程序印证了那读写锁逻辑

十四、条件变量实现生产者消费者模型

cpp 复制代码
条件变量:
    本身不是锁!但是通常结合锁来使用。mutex
    pthread_cond_t cond;

初始化条件变量:
    1.pthread_cond_init(&cond, NULL);    // 动态初始化。
    2.pthread_cond_t cond = PTHREAD_COND_INITIALIZER;    // 静态初始化
cpp 复制代码
/*借助条件变量模拟生产者-消费者问题*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

/*链表作为共享数据,需被互斥量保护*/
struct msg {
	int num;
	struct msg* next;
};

struct msg* head;

/*静态初始化一个条件变量和一个互斥量*/
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* consumer(void* arg) {
	struct msg* mp;

	while (1) {
		pthread_mutex_lock(&mutex);

		// 头指针为空,说明没有节点
		if (head == NULL) {		
			// 阻塞等待条件变量,解锁,直到这个函数返回才重新加锁
			pthread_cond_wait(&has_data, &mutex);
		}

		mp = head;
		head = mp->next;		// 模拟消费掉一个产品

		pthread_mutex_unlock(&mutex);		// 解锁互斥量
		printf("------Consume:%d\n", mp->num);

		free(mp);
		sleep(rand() % 3);
	}

	return NULL;
}

void* producer(void* arg) {
	struct msg* mp = malloc(sizeof(struct msg));

	while(1) {
		mp->num = rand() % 1000 + 1;		// 模拟生产一个产品
		printf("--Produce--- %d\n", mp->num);

		pthread_mutex_lock(&mutex);		// 加锁
		mp->next = head;
		head = mp;
		pthread_mutex_unlock(&mutex);	// 解锁

		pthread_cond_signal(&has_data);	// 将等待在该条件变量上的一个线程唤醒

		sleep(rand() % 3);
	}

	return NULL;
}

int main(int argc, char* argv[]) {
	int ret;
	pthread_t pid, cid;

	srand(time(NULL));

	ret = pthread_create(&pid, NULL, producer, NULL);	// 生产者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create producer error", strerror(ret));
		pthread_exit(NULL);
	}

	ret = pthread_create(&cid, NULL, consumer, NULL);	// 消费者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create consumer error", strerror(ret));
		pthread_exit(NULL);
	}

	pthread_join(pid, NULL);
	pthread_join(cid, NULL);

	return 0;
}

十五、多个消费者一个生产者模型

cpp 复制代码
/*借助条件变量模拟生产者-消费者问题*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

/*链表作为共享数据,需被互斥量保护*/
struct msg {
	int num;
	struct msg* next;
};

struct msg* head;

/*静态初始化一个条件变量和一个互斥量*/
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* consumer(void* arg) {
	struct msg* mp;

	while (1) {
		pthread_mutex_lock(&mutex);

		// 头指针为空,说明没有节点
		while (head == NULL) {	// 使用循环重复判断临界区是否有数据	
			// 阻塞等待条件变量,解锁,直到这个函数返回才重新加锁
			pthread_cond_wait(&has_data, &mutex);
		}

		mp = head;
		head = mp->next;		// 模拟消费掉一个产品

		pthread_mutex_unlock(&mutex);		// 解锁互斥量
		printf("------Consume id: %lu :%d\n", pthread_self(), mp->num);

		free(mp);
		sleep(rand() % 3);
	}

	return NULL;
}

void* producer(void* arg) {
	struct msg* mp = malloc(sizeof(struct msg));

	while(1) {
		mp->num = rand() % 1000 + 1;		// 模拟生产一个产品
		printf("--Produce--- %d\n", mp->num);

		pthread_mutex_lock(&mutex);		// 加锁
		mp->next = head;
		head = mp;
		pthread_mutex_unlock(&mutex);	// 解锁

		pthread_cond_signal(&has_data);	// 将等待在该条件变量上的一个线程唤醒

		sleep(rand() % 3);
	}

	return NULL;
}

int main(int argc, char* argv[]) {
	int ret;
	pthread_t pid, cid;

	srand(time(NULL));

	ret = pthread_create(&pid, NULL, producer, NULL);	// 生产者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create producer error", strerror(ret));
		pthread_exit(NULL);
	}

	ret = pthread_create(&cid, NULL, consumer, NULL);	// 消费者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create consumer error", strerror(ret));
		pthread_exit(NULL);
	}

    ret = pthread_create(&cid, NULL, consumer, NULL);	// 消费者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create consumer error", strerror(ret));
		pthread_exit(NULL);
	}

    ret = pthread_create(&cid, NULL, consumer, NULL);	// 消费者
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", "pthread_create consumer error", strerror(ret));
		pthread_exit(NULL);
	}

	pthread_join(pid, NULL);
	pthread_join(cid, NULL);

	return 0;
}

十六、用信号量实现生产者消费者模型

cpp 复制代码
/*信号量实现 生产者 消费者问题*/
//这才是真正的PV操作
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<stdio.h>
#include<semaphore.h>

#define NUM 5
int queue[NUM];		// 全局数组实现环形队列

sem_t blank_number, product_number;		// 空格子信号量,产品信号量


void* producer(void* arg) {
	int i = 0;

	while (1) {
		sem_wait(&blank_number);		// 生产者将空格子数--,为0则阻塞等待

		queue[i] = rand() % 1000 + 1;	// 生产一个产品

		printf("---Produce--%d\n", queue[i]);
		sem_post(&product_number);		// 将产品数++

		i = (i + 1) % NUM;				// 借助下标实现环形

		sleep(rand() % 1);
	}
}

void* consumer(void* arg)
{
	int i = 0;

	while (1) {
		sem_wait(&product_number);		// 消费者将产品数--,为0则阻塞等待

		printf("-Consume---%d\n", queue[i]);
		queue[i] = 0;					// 消费一个产品

		sem_post(&blank_number);		// 消费掉以后,将空格子数++

		i = (i + 1) % NUM;
		sleep(rand() % 3);
	}
}

int main(int argc, char* argv[]) {
	pthread_t pid, cid;

	sem_init(&blank_number, 0, NUM);	// 初始化空格子信号量为5
	sem_init(&product_number, 0, 0);	// 产品数为0

	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, consumer, NULL);

	pthread_join(pid, NULL);
	pthread_join(cid, NULL);

	sem_destroy(&blank_number);
	sem_destroy(&product_number);

	return 0;
}
相关推荐
躺着数星星2 小时前
Linux中安装es
linux·elasticsearch·jenkins
带土13 小时前
32位ubuntu14.0.4安装chrome
linux·chrome
欢鸽儿3 小时前
Vivado综合通关指南:从IP打包失败到工具崩溃的四重考验
linux·ubuntu·fpga
9毫米的幻想3 小时前
【Linux系统】—— 程序地址空间
java·linux·c语言·jvm·c++·学习
梦想blog5 小时前
漏洞修复 CentOS x86_64 OpenSSH 升级操作文档
linux·运维·centos·ssh·漏洞修复
林开落L9 小时前
Linux 进程信号:从进阶特性到实战应用(下)
linux·运维·服务器·进程信号
口嗨农民工13 小时前
win10默认搜索APP和window设置控制命板
linux·服务器·c语言
qq_4557608514 小时前
cmake命令行工具介绍
linux·服务器
bruk_spp15 小时前
从pty驱动学习tty设备驱动加载
linux·学习