线程管理二

线程管理二
一、前言:
线程是进程内部的一条执行序列,那么对于一个进程中多个线程之间,数据的共享是怎么具体操作的呢,我们对线程之间,全局数据。堆区数据、文件描述符会进行测试,以及线程之间的同步。

二、线程之间数据共享测试:

全局数据共享测试:
代码如下:

data_pthread.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <pthread.h>

//全局变量
int data = 10;

void *fun(void *arg)
{
	data = 20;	
	pthread_exit("baby");//功能:结束线程,参数是传递线程结束信息
}


void main()
{
	pthread_t id;
	int res = pthread_create(&id,NULL,fun,NULL);
	assert(res == 0);
	sleep(2);
	printf("data == %d\n",data);
	char *p = NULL;
	pthread_join(id,(void *)&p);//等待线程结束,参数是接收线程结束信息
	printf("%s\n",p);
}
Linux下gcc命令:
gcc -o  data  data_pthread.c  -lpthread
运行:./data

结果:


我们一开始定义一个全局数据data = 10;然后函数线程进行修改,修改data= 20;通过主线程打印data的值。如果主线程打印是20,说明全局数据共享。由上面的运行结果得出,线程之间全局数据共享。

堆区数据共享测试:
代码如下:

heap_pthread.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <pthread.h>


int *p = NULL;

void *fun(void *arg)
{
	p = (int *)malloc(10);
	*p = 20;
}


void main()
{
	pthread_t id;
	int res = pthread_create(&id,NULL,fun,NULL);
	assert(res == 0);
	sleep(1);
	printf("p =  %d\n",*p);
	pthread_join(id,NULL);
}
Linux下gcc命令:
gcc -o   heap  heap_pthread.c   -lpthread
运行: ./heap

结果:


我们一开始定义一个指针,然后在函数线程中进行开辟空间,并且修改值为20,通过主线程进行打印,结果是20的话说明线程之间在堆区数据也共享,看运行结果则说明线程之间在堆区数据是共享的。


文件描述符共享:
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>

int fd = 0;
//判断同一进程下多线程对文件描述读共享
void *fun(void *arg)
{
    fd = open("lock.c",O_RDONLY);
	assert(fd != -1);
	if (fd == -1)
	{
		pthread_exit("0");
	}
	pthread_exit("1");
}

int main()
{
	pthread_t id;
	int res = pthread_create(&id,NULL,fun,NULL);
	assert(res == 0);
	sleep(2);
	char buff[128] = {0};
	while(1)
	{
		int n = read(fd,buff,127);
		if (n <= 0)
		{
			break;
		}
		printf("%s",buff);
	}
	printf("\n");
}
Linux下gcc命令:
gcc -o  file  file_pcb.c   -lpthread
运行:./file

结果:


从函数线程打开一个文件,而从主线程进行读取文件中的内容到屏幕,如果文件描述符共享则成功打印,由上述运行结果得出文件描述符在线程之间是共享的。

三、线程同步:访问临界资源控制

a. 用信号量实现线程同步:

信号量一个特殊类型的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作,在一个多线程程序中也是这样,所以当一个程序中有两个(多个)线程试图改变一个信号量的值,系统保证所有的操作都一次进行。
信号量的函数都以sem_开头。今天说的是线程中使用的信号量函数;

头文件:
semaphore.h

sem_init函数:初始化函数
int sem_init(sem_t  *sem,  int  pshared,  unsigned  int  value);
第一个参数sem指向的是信号量对象,设置它的共享选项,给它一个初始的整数值;
第二个参数pshared参数控制信号量的类型,如果其值为0,就表示这个信号量是的当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享;
第三个参数value是给信号量的初值;

P操作:int sem_wait(sem_t  *sem);     -1操作;通过sem_wait使得其他线程阻塞
V操作:int sem_post(sem_t  *sem);     +1操作;
两个函数的参数都是一个指针,这个指针指向的对象是sem_init调用初始化的信号量。

销毁:
int sem_destroy(sem_t  *sem);
参数是一个指向信号量的指针,并清理该信号量所拥有的所有资源。如果企图清理的信号量正被一些线程等待,就会收到一个错误。
返回值:以上这些函数成功都会返回0;

测试练习:
题目:主线程接收用户输入,函数线程统计输入的字符个数,当用户输入end的时候结束统计。

代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <assert.h>

#include <pthread.h>

#include <fcntl.h>

#include <semaphore.h>



sem_t sem1;

sem_t sem2;



void *work_thread(void *arg)

{

	char *buff = (char *)arg;

	while(1)

	{

		sem_wait(&sem1);

		if(strncmp(buff, "end", 3) == 0)

		{

			break;

		}

		int i = 0, count = 0;

		for(; buff[i] != 0; ++i)

		{

			count++;

		}



		printf("字符个数为:  %d\n", count);

		sem_post(&sem2);

	}

}





void main()

{

	char buff[128] = {0};

	pthread_t id;

	sem_init(&sem1, 0, 0);

	sem_init(&sem2, 0, 1);

	int res = pthread_create(&id, NULL, work_thread, (void *)buff);

	assert(res == 0);



	while(1)

	{

		sem_wait(&sem2);

		memset(buff, 0, 128);

		printf("请输入需要统计的字符: ");

		fflush(stdout);



		fgets(buff, 128, stdin);

		buff[strlen(buff) - 1] = 0;

		sem_post(&sem1);

		if(strncmp(buff, "end", 3) == 0)

		{

			break;

		}

	}

}
Linux下gcc命令:
gcc -o  sem  sem_pthread.c  -lpthread
运行:./sem

结果:


信号量sem1控制的是主线程给函数线程提供数据;
信号量sem2控制的是主线程得到函数线程的统计字符的结果,并继续由用户输入字符。

b.用互斥锁实现线程同步

理论:互斥量允许程序员锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作后解锁它。

头文件:pthread.h
对象  pthread_mutex_t   mutex;
初始化:int  pthread_mutex_init(pthread_mutex_t  *mutex,  const  pthread_mutexattr_t  *mutexattr);
加锁:int  pthread_mutex_lock(pthread_mutex_t  *mutex);
解锁:int  pthread_mutex_unlock(pthread_mutex_t  *mutex);
销毁:int pthread_mutex_destroy(pthread_mutex_t  *mutex);
参数:以上函数的参数mutex都是声明过的对象的指针。这个对象的类型为pthread_mutex_t 。
init的第二个参数是设置互斥量的属性。而属性控制着互斥量的行为。属性类型默认为fast。

设置属性的缺点:如果程序对一个加锁的互斥量再次调用加锁函数,这时候,程序就会阻塞,而又因为拥有互斥量的这个线程正是被阻塞的线程,所以互斥量永远也不会解锁了,程序也就进入了死锁的状态。解决方法:改变互斥量的属性,如果遇到上述情况就返回一个错误,或者让它递归操作,给同一个进程加多个锁,相应的后面执行相同数量的解锁操作。
平常我们会把第二个参数设置为 NULL传递给属性指针,也就是默认行为。

测试练习:
题目:主线程接收用户输入,函数线程统计输入的字符个数,当用户输入end的时候结束统计。
代码如下:

lock_pthread.c文件;

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <assert.h>

#include <pthread.h>

#include <fcntl.h>

#include <semaphore.h>



pthread_mutex_t mutex;



void *work_thread(void *arg)

{

	char *buff = (char *)arg;

	while(1)

	{

		pthread_mutex_lock(&mutex);

		if(strncmp(buff, "end", 3) == 0)

		{

			break;

		}

		int i = 0, count = 0;

		for(; buff[i] != 0; ++i)

		{

			count++;

		}



		printf("输入的字符个数为:  %d\n", count);

		pthread_mutex_unlock(&mutex);

		sleep(1);

	}

}





void main()

{

	char buff[128] = {0};

	pthread_t id;

	int res = pthread_create(&id, NULL, work_thread, (void *)buff);

	assert(res == 0);

	pthread_mutex_init(&mutex,NULL);

	while(1)

	{

		pthread_mutex_lock(&mutex);

		memset(buff, 0, 128);

		printf("请输入需要统计的字符: ");

		fflush(stdout);

		fgets(buff, 128, stdin);

		buff[strlen(buff) - 1] = 0;

		pthread_mutex_unlock(&mutex);

		if(strncmp(buff, "end", 3) == 0)

		{

			break;

		}

		sleep(1);

	}

	pthread_join(id,NULL);

	pthread_mutex_destroy(&mutex);

}
Linux下gcc命令:
gcc  -o  lock   lock_pthread.c  -lpthread
运行:./lock

结果:


在程序的开始我们声明了一个互斥量对象,然后在函数线程中进行初始化互斥量。主线程首先试图对互斥量加锁,。如果它已经锁住,这个调用将被阻塞直到它被释放为止,当用户字符输入完毕后,主线程会解锁,这时候,函数线程会进行字符统计,并加锁,防止主线程操作临界资源,当函数线程统计字符完成后,会进行解锁操作,这时候主线程才能继续让用户输入字符。也就是周期性地循环操作加锁,解锁,直到遇到用户输入end,结束程序。

c.条件变量解决线程同步

条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时、允许进程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量使用之前必须首先进行初始化。
头文件:pthread.h
条件变量类型:pthread_cond_t;

初始化函数:
int  pthread_cond_init(pthread_cond_t  *restrict  cond,  pthread_condattr_t  *restrict  attr);
attr参数设置为NULL;
在释放底层的内存空间之前,使用pthread_mutex_destroy函数对条件变量进行去初始化:

条件变量销毁:
int  pthread_cond_destroy(pthread_cond_t  *cond);

pthread_cond_wait函数:
使用pthread_cond_wait 等待条件变为真;
int  pthread_cond_wait(pthread_cond_t  *restrict  cond,pthread_mutex_t  *restrict  mutex);
传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。
当pthread_cond_wait返回时,互斥量再次被锁住。

用于通知线程条件已经满足的两个函数:
pthread_cond_signal函数:
唤醒等待该条件的某个线程;
int  pthread_cond_signal(pthread_cond_t  *cond);

pthread_cond_broadcast函数:
唤醒等待该条件的所有线程;
int pthread_cond_broadcast(pthread_cond_t  *cond);
两个函数返回值:成功返回0;失败返回错误编号;

调用pthread_cond_signal或者pthread_cond_broadcast,也称为向线程或条件发送信号。必须注意一定要在改变条件状态以后再给线程发送信号。

四、线程安全------可重入函数
首先,编写以下代码:

文件:safe_pthread1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <string.h>


void *fun(void *arg)
{
	char arr[] = "a b c d e f g";
	char *p = strtok(arr," ");
	while(p != NULL)
	{
		printf("fun: %s\n",p);
		sleep(1);
		p = strtok(NULL," ");
	}

}

void main()
{
	pthread_t id;
	int res = pthread_create(&id ,NULL,fun,NULL);
	assert(res == 0);
	char buff[] = "1 2 3 4 5 6 7";
	char *p = strtok(buff," ");
	while(p != NULL)
	{
		printf("main : %s\n",p);
		sleep(1);
		p = strtok(NULL, " ");
	}
}
Linux下gcc命令:
gcc  -o  safe1  safe_pthread1.c  -lpthread
运行:./safe1

结果:


按照正常逻辑,应该是交替打印1,a,2,b,3,c,4,d,5,e,6,f,7,g。但是只打印了数字只有一个1,再的全是字母,这是为什么呢?

说到这里,就要提出线程安全的定义了,如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是安全的,而我们这里的strtok函数在POSIX.1中是不能保证线程安全的函数。所以它出现了这种多个控制线程在同一时间潜在的调用了strtok这个不安全的函数,使得出现意想不到的结果。那么关于这些不安全的函数还有哪些呢?


以上都是不能保证线程安全的函数;
那么怎么解决呢?
针对以上可能不安全的函数,操作系统对支持线程安全的这一特性时,对非线程安全函数提供了可替代的安全版本;如下图:


那么我们对以上代码进行修改:
代码如下:

文件:safe_pthread.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <string.h>


void *fun(void *arg)
{
	char arr[] = "a b c d e f g";
	char *q = NULL;
	char *p = strtok_r(arr," ",&q);
	while(p != NULL)
	{
		printf("fun: %s\n",p);
		sleep(1);
		p = strtok_r(NULL," ",&q);
	}

}

void main()
{
	pthread_t id;
	int res = pthread_create(&id ,NULL,fun,NULL);
	assert(res == 0);
	char buff[] = "1 2 3 4 5 6 7";
	char *q = NULL;
	char *p = strtok_r(buff," ",&q);
	while(p != NULL)
	{
		printf("main : %s\n",p);
		sleep(1);
		p = strtok_r(NULL, " ", &q);
	}
}
Linux下gcc命令:
gcc -o  safe  safe_pthread.c   -lpthread
运行:./safe

结果:


以上使用正确的安全版本的strtok_r函数,所运行得到的结果才是我们想要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值