linux--线程

x33g5p2x  于2021-12-06 转载在 Linux  
字(16.6k)|赞(0)|评价(0)|浏览(268)

1线程是系统调度分配的最小单位。

线程概述

线程操作

创建线程 pthread_create

例子:使用pthread_ereate()函数创建线程,并使原线程与新线程分别打印自己的线程id。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg)
{
	printf("tfn--pid=%d,tid=%lu\n", getpid(), pthread_self());
	return (void*)0;
}
int main()
{
	pthread_t tid;
	printf("main--pid=%d,tid=%lu\n", getpid(), pthread_self());
	int ret = pthread_create(&tid, NULL, tfn, NULL);
	if (ret != 0){
		fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
		exit(1);
	}
	sleep(1);
	return 0;
}

执行成功后,进程4800中的二个线程分别打印出来各自的线程id,由此可知成功

//

进程拥有独立的地址空间。
当使用fork()函数创建出新进程后,若其中一个进程要对fork()之前的数据进行修改,进程中会依据“写时复制”原则,先复制一份该数据到子进程的地址空间,再修改数据。因此即便是全局变量,在进程间也是不共享的。但由于线程间共享地址空间,因此在一个线程中对全局区的数据进行修改,其他线程中访问到的也是修改后的数据。下面通过一个简单案例对此进行验证。

案例9-2:创建新线程,在新线程中修改原线程中定义在全局区的变量,并在原线程中打印该数据。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
int var = 100;
void *tfn(void *arg)
{
	var = 200;
	printf("thread\n");
	return NULL;
}
int main(void)
{
	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;
}

由此说明源线程中访问到的变量var的值被修改为200,说明新线程成功修改了原线程中定义的全局变量,线程之间共享全局数据

线程退出 pthread_exit

案例3:在一个进程中创建4个新线程,分别使用pthread.exit()函数、return、exit()使其中一个线程退出,观察其它线程的执行状况。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//线程运行函数
void *tfn(void *arg) //传回i
{
	long int i;
	i = (long int)arg; //将void* 类型的arg强转为long int类型
	if (i == 4)
		pthread_exit(NULL); 
	sleep(i);	 						//通过i来区别每个线程
	printf("I'm %dth thread, Thread_ID = %lu\n", i + 1, pthread_self());
	return NULL;
}

int main(int argc, char *argv[])
{
	long int n = 5, i;
	pthread_t tid;
	for (i = 0; i < n; i++) {
		//将i转换为指针,在tfn中再强转回整形
		pthread_create(&tid, NULL, tfn, (void *)i);
	}
	sleep(n);
	printf("I am main, I'm a thread!\n"
		"main_thread_ID = %lu\n", pthread_self()); //主线程
	return 0;
}

如果把pthread_exit()前面的i改成2就退出了

那么printf(“I’m %dth thread, Thread_ID = %lu\n”, i + 1, pthread_self());
i=3的就没有了

线程终止 pthread_cancel

说明:
与线程不同的是,调用pthread.cancel()函数后,指定的线程并不会立刻被杀死,而需要等待线程到达某个取消点,线程才会成功被终止.

类似游戏到达存档点

所谓取消点即在线程执行过程中会检测是否有未响应取消信号的点,可粗略地认为只要有系统调用(进入内核)发生,就会进入取消点,如在程序中调用read()、write()、pause()等函数时都会出现取消点。取消点通常伴随阻塞出现,用户也可以在程序中通过调用pthread_testcancel()函数创造取消点。

案例9-4:在程序中使用pthread_cancel()函数使原线程终止指定线程。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void *tfn(void *arg)
{
	while (1) {
		printf("child thread...\n");
		pthread_testcancel();   			//设置取消点
	}
}
int main(void)
{
	pthread_t tid;
	void *tret = NULL;
	pthread_create(&tid, NULL, tfn, NULL);
	sleep(1);
	pthread_cancel(tid);
	pthread_join(tid, &tret); //用户定义指针,返回函数结果
	printf("child thread exit code = %ld\n", (long int)tret);
	return 0;
}

prhread_exit()和pthread_cancel()都是线程机制中提供的用于终止线程的系统调用,
pthread_exit()使线程主动退出,pthread_cancel()通过信号使线程被动退出。

需要注意的是,由于在线程机制出现之前信号机制已经出现,信号机制在创建时并未将线程考虑在内,线程与信号机制的兼容性略有不足,因此在多线程编程中应尽量避免使用信号,以免出现难以调试的错误。

线程挂起 pthread_join

案例5:使用pthread_exit()退出线程,为线程设置退出状态,并将线程的退出状态输出。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct{
	int a;
	int b;
} exit_t;

void *tfn(void *arg)
{
	exit_t *ret;
	ret = malloc(sizeof(exit_t)); //申请exit_t空内存 void*
	ret->a = 100;
	ret->b = 300;
	pthread_exit((void *)ret);	//线程终止
	return NULL; 				//线程返回
}

int main(void)
{
	pthread_t tid;
	exit_t *retval;
	pthread_create(&tid, NULL, tfn, NULL);
	//调用pthread_join可以获取线程的退出状态
	pthread_join(tid, (void **)&retval);
	printf("a = %d, b = %d \n", retval->a, retval->b);
	return 0;
}

在案例9-5创建的新线程中,既调用了pthread_exit()函数,又设置了关键字return:在程序的第24行中,使用pthread_join()等待新线程退出并获取线程的退出状态,若第25行代码中打印的线程退出状态不为空,说明线程通过pthread_exit()函数退出。

由执行结果可知,第15行调用的ptbreed_exit()函数成功使线程退出,并设置了线程的退出状态。

进程中可以使用waitpid()函数结合循环结构使原进程等待多个进程退出,线程中的pthread_join()同样可以与循环结构结合,等待多个线程退出。

案例9-6:使用pthread_join()回收多个新线程,并使用pthread_exit()获取每个线程的退出状态。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
long int var = 100;
void *tfn(void *arg)
{
	long int i;
	i = (long int)arg;
	sleep(i);
	if (i == 1) {
		var = 333;
		printf("var = %d\n", var);
		pthread_exit((void *)var);
	}
	else if (i == 3) {
		var = 777;
		printf("I'm %dth pthread, pthread_id = %lu\n"
			" var = %d\n", i + 1, pthread_self(), var);
		pthread_exit((void *)var);
	}
	else {
		printf("I'm %dth pthread, pthread_id = %lu\n"
			" var = %d\n", i + 1, pthread_self(), var);
		pthread_exit((void *)var);
	}
	return NULL;
}
int main(void)
{
	pthread_t tid[5];
	long int i;
	int *ret[5];
	for (i = 0; i < 5; i++)				//创建新线程
		pthread_create(&tid[i], NULL, tfn, (void *)i);
	for (i = 0; i < 5; i++) {			//回收新线程
		pthread_join(tid[i], (void **)&ret[i]);
		printf("-------%d 's ret = %d\n", i, (long int)ret[i]);
	}
	printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);
	pthread_exit(NULL);
}

由执行结果可知,程序中创建的5个新线程都成功退出,案例9-6成功运行。

当然,原线程的退出之所以会导致其他线程退出,是因为原线程执行完毕后,main()函数中会隐式调用exit()函数,而我们知道pthread_exit()函数可以只使调用该函数的线程退出。
因此,若在原线程调用return之前调用pthread_exit(),同样可保证其他线程的正常运行。

线程分离 pthread_detach

案例7:使用pthread_detach()函数分离新线程,使新线程自动回收。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void *tfn(void *arg)
{
	int n = 5;
	while (n--) {
		printf("pthread tfn n = %d\n", n);
		sleep(1);
	}
	return (void *)7;
}
int main(void)
{
	pthread_t tid;
	void *ret;
	pthread_create(&tid, NULL, tfn, NULL);
	pthread_detach(tid);				//分离新线程
	int retvar = pthread_join(tid, (void **)&ret);
	if (retvar != 0) {
		fprintf(stderr, "pthread_join error %s\n", strerror(retvar));
	}
	else
	{
		printf("pthread exit with %ld\n", (long int)ret);
	}
	return 0;
}

综合程序来说,第6~14行定义了tfn()函数。在第19行代码中,tfn()函数作为新线程的执行函数传递给pthread_create()函数。第20行代码调用pthread_detach()函数将第19行代码创建的新线程从当前线程中分离出来。第21行代码调用pthread_join函数将新线程挂起,新线程终止后,pthread_join()函数中的参数ret将获取线程的终止状态。

由程序执行结果可以看出,程序执行了第27行代码。结合程序分析可知,若pthread join()函数调用成功,应打印“(long int)ret”,即长整型数值7,但执行结果中打印的“(long int)ret”显然不为7。说明pthread_join()函数调用失败,由此反证pthread_detach()函数调用成功,此时新线程已处于分离状态。若新线程有机会执行,在其执行完毕后,会自动释放自身占用的全部资源。

线程属性

概述

线程的分离状态:

线程的调度策略:

线程的调度参数:

线程的继承性:

线程的作用域:

线程的栈:

案例9-8:在程序中通过设置线程属性的方式设置线程分离状态和线程内部栈空间容量及栈地址,使程序不断创建线程,耗尽内存空间并打印线程编号。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define SIZE 0x90000000
void *th_fun(void *arg)
{
	while (1)
		sleep(1);
}
int main()
{
	pthread_t tid;					//线程id
	int err, detachstate;
	int i = 1;
	pthread_attr_t attr;				//线程属性
	size_t stacksize;				//栈容量
	void *stackaddr;					//栈地址
	pthread_attr_init(&attr);		//初始化线程属性结构体
	//获取程栈地址、栈容量
	pthread_attr_getstack(&attr, &stackaddr, &stacksize);
	//获取线程分离状态
	pthread_attr_getdetachstate(&attr, &detachstate);
	//判断线程分离状态
	if (detachstate == PTHREAD_CREATE_DETACHED)
		printf("thread detached\n");
	else if (detachstate == PTHREAD_CREATE_JOINABLE)
		printf("thread join\n");
	else
		printf("thread un known\n");
	//设置线程分离状态,使线程分离
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	while (1) {
		//在堆上申请内存,指定线程栈的起始地址和大小
		stackaddr = malloc(SIZE);
		if (stackaddr == NULL) {
			perror("malloc");
			exit(1);
		}
		stacksize = SIZE;
		//设置线程栈地址和栈容量
		pthread_attr_setstack(&attr, stackaddr, stacksize);
		//使用自定义属性创建线程
		err = pthread_create(&tid, &attr, th_fun, NULL);
		if (err != 0) {
			printf("%s\n", strerror(err));
			exit(1);
		}
		i++;
		printf("%d\n", i);				//打印线程编号
	}
	pthread_attr_destroy(&attr);		//销毁attr资源
	return 0;
}

效果:

线程同步

出现与事件有关的错误的原因有三个:
(1)资源共享;
(2)调度随机;
(3)线程问缺之必要的同步机制。

若要实现线程同步,应满足以下两个条件:
(1)在一个线程发起功能调用请求访问共享资源时,若尚未取得结果,该调用不返回;
(2)与此同时,其它线程不能再调用该功能访问共享资源。

同步的目的是避免数据混乱,解决与时间有关的错误,实际上,不仅线程间需要同步,所有“多个控制流共同操作一个共享资源”的情况,都需要同步。

Linux系统中常用于实现线程同步的方式有三种,分别为:互序锁、条件变量与信号量。

1 互斥锁

使用互压锁实现线程同步时,系统会为共享资源添加一个称为互斥锁的标记防止多个线程在同一时刻访问相同的共用资源。即防止上面银行同时存取钱的情况

互斥锁通常也被称为互斥量(mutex),它相当于一把锁,使用互序锁可以保
证以下3点:
(1)原子性:如果在一个线程中设置了一个互斤锁,那么在加锁与解锁之间的操作会被锁定为一个原子操作;这些操作要么全部完成,要么一个也不执行。
(2)唯一性:
(3)非繁忙等待

1.初始化互斥锁 pthread_mutex_init()

pthread_mutex_init()函数的功能为初始化互斥锁,该函数的声明如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);

pthread_mutex_init()函数中参数mutex为一个pthread_mutex_t*类型的传入传出参数,关于该参数有以下几个要点:
pthread_mutext_t类型的本质是结构体,为简化理解,读者可将其视为整型;
②变量mutex只有两种取值:0和1,加锁:mutex-1;解锁:mutex+1;
③参数mutex之前的restrict是一个关键字,该关键字用于限制指针,其功能为告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。

函数中第二个参数attr同样是一个传入参数,代表互斥量的属性,通常传递NULL,表示使用默认属性。

若函数pthread_mutex_init()调用成功则返回0,否则返回errno,errno的常见取值为EAGAINEDEADLK,其中EAGAIN表示超出互斥锁递归锁定的最大次数,因此无法获取该互斥锁;EDEADLK表示当前线程已有互斥锁,二次加锁失败。

通过pthread_mutex_init()函数初始化互斥量又称为动态初始化,一般用于初始化局部变量,示例如下:

pthread_mutex_init(&mutex, NULL);

此外互斥锁也可以直接使用宏进行初始化:

pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;

效果相同

2.加锁 pthread_mutex_lock()

当在线程中调用pthread_mutex_lock()函数时,该线程将会锁定指定互斥量pthread_mutext_lock()函数的声明如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

若函数pthread_mutex_lock()调用成功则返回0,否则返回errno。

若需要使用的互斥锁正在被使用,调用pthread_ mutex_ lock()函数的线程会进入阻塞。

但有些情况下,我们希望线程可以先去执行其他功能,此时需要使用非阻塞的互斥锁

Linux系统中提供了pthread__mutex_trylock()函数,该函数的功能为尝试加锁;若锁正在被
使用,则不阻塞等待,而是直接返回并返回错误号pthread_ mutex_ trylock()函数的声明
如下:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

该函数中的参数mutex同样表示待锁定的互斥量;若函数调用成功则返回0,否则返回errno,其中常见的errno有两个,分别为EBUSYEAGAIN,它们代表的含义如下:

  1.  EBUSY:参数mutex指向的互斥锁已锁定;
  2.  EAGAIN:超过互斥锁递归锁定的最大次数。

3.解锁 pthread_mutex_unlock()

当在线程中调用pthread_mutex_unlock()函数时,该线程将会为指定互斥量解锁。pthread_mutext_unlock()函数的声明如下:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

函数中的参数mutex表示待解锁的互斥量。若函数pthread_mutex_lock()调用成功则返回0,否则返回errno。

4. 释放销毁锁 pthread_mutex_destroy()

当在线程中调用pthread_mutex_destroy()函数时,该线程将会为指定互斥量解锁pthread_mutext_destroy()函数的声明如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

函数中的参数mutex表示待销毁的互斥量。若函数pthread_mutex_lock()调用成功则返回0,否则返回errno。

例子

案例9:在原线程和新线程中分别进行打印操作,使原线程分别打印“HELLO”、“ WORLD”,新线程分别打印“hello”、“world”。

//未添加mutex
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *tfn(void *arg)
{
	srand(time(NULL));
	while (1) {
		printf("hello ");
		//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
		sleep(rand() % 3);
		printf("world\n");
		sleep(rand() % 3);
	}
	return NULL;
}
int main(void)
{
	pthread_t tid;
	srand(time(NULL));
	pthread_create(&tid, NULL, tfn, NULL);
	while (1) {
		printf("HELLO ");
		sleep(rand() % 3);
		printf("WORLD\n");
		sleep(rand() % 3);
	}
	pthread_join(tid, NULL);
	return 0;
}

从上面可知,原线程与新线程中的字符串未能成对打印。

在上面的程序中添加互斥量,进行线程同步。

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t m;						//定义互斥锁
void err_thread(int ret, char *str)
{
	if (ret != 0) {
		fprintf(stderr, "%s:%s\n", str, strerror(ret));
		pthread_exit(NULL);
	}
}
void *tfn(void *arg)
{
	srand(time(NULL));
	while (1) {
		pthread_mutex_lock(&m);     		//加锁:m--
		printf("hello ");
		//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
		sleep(rand() % 3);
		printf("world\n");
		pthread_mutex_unlock(&m);   		//解锁:m++
		sleep(rand() % 3);
	}
	return NULL;
}
int main(void)
{
	pthread_t tid;
	srand(time(NULL));
	int flag = 5;
	pthread_mutex_init(&m, NULL);        	//初始化mutex:m=1
	int ret = pthread_create(&tid, NULL, tfn, NULL);
	err_thread(ret, "pthread_create error");
	while (flag--) {
		pthread_mutex_lock(&m);     		//加锁:m--
		printf("HELLO ");
		sleep(rand() % 3);
		printf("WORLD\n");
		pthread_mutex_unlock(&m);     	//解锁:m--
		sleep(rand() % 3);
	}
	pthread_cancel(tid);
	pthread_join(tid, NULL);
	pthread_mutex_destroy(&m);
	return 0;
}

5次,flag等于5
成对输出,同步,线程加锁成功

2 条件变量

1.初始化条件变量 pthread_cond_init()

Linux系统中用于初始化条件变量的函数为pthread_cond_init(),其声明如下:

int pthread_cond_init(pthread_cond_t *restrict cond,
                          const pthread_condattr_t *restrict attr);

参数cond代表条件变量,本质是一个指向pthread_cond_t类型的结构体指针,pthread_cond_t是Linux系统中定义的条件变量类型

参数attr代表条件变量的属性,通常设置为NULL,表示使用默认属性初始化条件变量,其默认值为PTHREAD_PROCESS_PRIVATE表示当前进程中的线程共用此条件变量
也可将attr设置为PTHREAD_PROCESS_SHARED表示多个进程间的线程共用条件变量

函数pthread调用成功则返回0,否则返回-1,并设置errno。

除使用pthread_cond_init()动态初始化条件变量外,也可以用如下语句以静态方法初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

此种方式与将attr参数初始化为NULL的pthread_cond_init()函数等效,但是不进行错误检查。

2 阻塞等待条件变量满足 pthread_cond_wait()

Linux系统中一般通过pthread_cond_wait()函数,使线程进入阻塞状态,等待一个条件变量,其声明如下:

int pthread_cond_wait(pthread_cond_t *restrict cond,
                          pthread_mutex_t *restrict mutex);

函数pthread_cond_wait()中的参数cond代表条件变量;参数mutex代表与当前线程绑定的互斥锁。若该函数调用成功则返回0,否则返回-1,并设置errno。

pthread_cond_wait()外,pthread_cond_timedwait()也能使线程阻塞等待条件变量,不同的是,该函数可以指定线程的阻塞时长,若等待超时,该函数便会返回。

函数pthread_cond_timedwait()存在于函数库pthread.h中,其声明如下:

int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                                pthread_mutex_t *restrict mutex,
                                const struct timespec *restrict abstime);

struct timespec类型的结构体指针,该结构体的定义如下:

struct timespec {
	time_t tv_sec;		//秒
	long   tv_nsec;		//纳秒
}

3 唤醒条件变量 pthread_cond_signal或broadcast

pthread_cond_signal()函数会在条件变量满足之后,以信号的形式唤醒阻塞在该条件变量的一个线程。处于阻塞状态中的线程的唤醒顺序由调度策略决定。

pthread_cond_signal()函数存在于函数库pthread.h中,其声明如下:

int pthread_cond_signal(pthread_cond_t *cond);

函数pthread_cond_signal()中的参数cond代表条件变量,若该函数调用成功则返回0,否则返回-1,并设置errno。

pthread_cond_broadcast()函数同样唤醒阻塞在指定条件变量的线程,不同的是,该函数会以广播的形式,唤醒阻塞在该条件变量上的所有线程pthread_cond_broadcast()函数存在于函数库pthread.h中,其声明如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

函数pthread_cond_broadcast()中的参数cond代表条件变量,若该函数调用成功则返回0,否则返回-1,并设置errno。

4 销毁条件变量 pthread_cond_destroy

pthread_cond_destory()函数用于销毁条件变量,该函数的声明如下:

int pthread_cond_destory(pthread_cond_t *cond);

需要注意的是,只有当没有线程在等待参数cond指定的条件变量时,才可以销毁条件变量,否则该函数会返回EBUSY。

案例

案例10:生产者-消费者模型是线程同步中的一个经典案例。假设有两个线程,这两个线程同时操作一个共享资源(一般称为汇聚),其中一个模拟生产者行为,生产共享资源,当容器存满时,生产者无法向其中放入产品;另一个线程模拟消费者行为,消费共享资源,当产品数量为0时,消费者无法获取产品,应阻塞等待。显然,为防止数据混乱,每次只能由生产者、消费者中的一个,操作共享资源。本案例要求使用程序实现简单的生产者-消费者模型(可假设容器无限大)。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
struct msg {
	struct msg *next;
	int num;
};
struct msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;	//初始化条件变量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;		//初始化互斥锁
//消费者
void *consumer(void *p)
{
	struct msg *mp;
	for (;;) {
		pthread_mutex_lock(&lock);						//加锁
		//若头结点为空,表明产品数量为0,消费者无法消费产品
		while (head == NULL) {
			pthread_cond_wait(&has_product, &lock);		//阻塞等待并解锁
		}
		mp = head;
		head = mp->next;    					//模拟消费一个产品
		pthread_mutex_unlock(&lock);
		printf("-Consume ---%d\n", mp->num);
		free(mp);
		sleep(rand() % 5);
	}
}
//生产者
void *producer(void *p)
{
	struct msg *mp;
	while (1) {
		mp = malloc(sizeof(struct msg));
		mp->num = rand() % 1000 + 1;        	//模拟生产一个产品
		printf("-Produce ---%d\n", mp->num);
		pthread_mutex_lock(&lock);			//加锁
		mp->next = head;						//插入结点(添加产品)
		head = mp;
		pthread_mutex_unlock(&lock);			//解锁
		pthread_cond_signal(&has_product);  	//唤醒等待在该条件变量上的一个线程
		sleep(rand() % 5);
	}
}
int main(int argc, char *argv[])
{
	pthread_t pid, cid;
	srand(time(NULL));
	//创建生产者、消费者线程
	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, consumer, NULL);
	//回收线程
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	return 0;
}

案例9-10中的

第5→8行代码定义了一个链表结点,用于存储生产者线程创建的资源;

第9行代码定义了链表的头结点,该链表是一个全局变量,因此是所有线程都可访问的公有
资源;

第10、11两行分别定义并初始化了互斥锁与条件变量;

第13~29行代码中的函数 consumer()用于模拟消费者的行为;

第31~45行代码中的函数producer()用于模拟生产者的行为;

第46~57行代码为主程序,主要用于创建生产者、消费者线程,以及执行线程的回收工作。

3 信号量

互斥锁初值为1,只能有两个值,加锁则为-1,解锁则为1。
互斥锁唯一且非空闲等待的特性使得线程由并行执行变为了串行执行,削弱了线程的并发性。
假设多个线程需要共享的资源不唯一,例如打印店中的多台计算机连接了多台打印机,每台计算机能通过任意一台打印机打印文件,那么在进行打印任务时,“共享资源"(即打印机)显然有多个。若此时仍要使用互斥锁来锁定共享资源,则需要创建多个互斥锁,且需要使每个线程尝试申请互斥锁,这显然比较麻烦。

多线程编程中使用信号量机制解决这一问题。
线程中的信号量是互斥锁的升级,其初值不再设置为1,而是设置为N。
多线程中使用到的信号量与进程通信中讲解的信号量在本质上没有区别。

1 初始化信号量 sem_init()

sem_init()函数的声明如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数sem为指向信号量变量的指针;
参数pshared用于控制信号量的作用范围,其取值通常为0与非0,当pshared被设置为0时,信号量将会被放在进程中所有线程可见的地址内,由进程中的线程共享;当pshared被设置为非0值时,信号量将会被放置在共享内存区域,由所有进程共享。
参数value用于设置信号量sem的初值。

若函数sem_init()执行成功则返回0,否则返回-1,并设置errno。

2 阻塞等待信号量 seem_wait()

sem_wait()函数的声明如下:

int sem_wait(sem_t *sem);

其中参数sem为指向信号量变量的指针;

sem_wait()函数对应P操作,若调用成功,则会使信号量sem的值减一,并返回0;
若调用失败,则返回-1,并设置errno。

3 唤醒阻塞线程 seem_post()

sem_post()函数的声明如下:

int sem_post(sem_t *sem);

其中参数sem为指向信号量变量的指针;

sem_init()函数对应V操作,若调用成功,则会使信号量sem的值加一,并返回0;
若调用失败,则返回-1,并设置errno。

4. 释放信号量 seem_destroy()

与互斥锁类似,信号量也是一种系统资源,使用完毕之后应主动回收,Linux系统中用于回收信号量的函数为sem_destroy(),其声明如下:

int sem_destroy(sem_t *sem);

sem_destroy()中参数sem为指向信号量变量的指针;
若函数调用成功,则会使信号量sem的值加一,并返回0;
若调用失败,则返回-1,并设置errno。

sem_getvalue()

线程中另有一个常用的系统调用,即sem_getvalue(),该函数的功能为获取系统中当前信号量的值,其函数声明如下:

int sem_getvalue(sem_t *sem, int *sval);

其中参数sem为指向信号量变量的指针,参数sval为一个传入指针,用于获取信号量的值,在程序中调用该函数后,信号量sem的值会被存储在参数sval中。

案例

案例11:本案例也来实现一个模拟生产者-消费者模型,但对生产者进行限制:若容器已满,生产者不能生产,需等待消费者消费。

#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\t%lu\n", queue[i], pthread_self());
		queue[i] = 0;                               	//消费一个产品 
		sem_post(&blank_number);                 	//消费掉以后,将空格子数++
		i = (i + 1) % NUM;
		sleep(rand() % 1);
	}
}
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_create(&cid, NULL, consumer, NULL);
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	sem_destroy(&blank_number);
	sem_destroy(&product_number);
	return 0;
}

相关文章

微信公众号