Linux线程

线程

  • 线程是一个进程内部的控制序列,所有进程都至少存在一个执行进程。

进程和线程的区别

  • 进程是资源竞争的基本单位。
  • 线程是程序执行的最小单位。
  • 线程共享进程数据,但也拥有自己的一部分数据。

每个进程可以拥有多个线程,但至少必须有一个线程。

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多。
  • 与进程之间的切换相比,线程之间的切换比较容易。
  • 线程占用的资源要比进程少很多。
  • 能充分利用多处理器的可并行数量。
  • 在等待I/O操作结束的同时,程序可执行其他的计算机任务。
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以等待不同的I/O操作。

线程的缺点

  • 性能缺失。(增加了额外的同步和调度开销, 但是可用的资源不变)
  • 健壮性降低。(线程和线程之间缺乏保护)
  • 缺乏访问控制。(在一个线程中调用某些OS函数会影响进程)
  • 编程难度提高。(多线程比单线程编写与调试难度高)

线程控制

  • 与线程有关的大多数函数的名字都是以pthread_开头的。
  • 使用这些函数,需要引入头文件。
  • 链接这些线程函数库时要使用编译器命令的-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;失败返回错误码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *rout(void* arg)
{
	while(1)
	{
		printf("I am another pthread!\n");
		sleep(2);
	}
}
int main()
{
	int ret;
	pthread_t t;
	if((ret=pthread_create(&t,NULL,rout,NULL))!=0)
		perror("create pthread\n");
	while(1)
	{
		printf("I am main pthread!\n");
		sleep(2);
	}
	return 0;
}

由执行结果可看出,的确创建出了另外一个线程,也确实执行了相应的函数。

进程ID和线程ID

描述进程的是pid,描述线程的是tid。

                pid_t tid;
		tid = syscall(SYS_gettid);
		printf("tid = %d\n",tid);

这里我们加上几条查看线程ID的语句,就变成了如下结果。


我们就获取到了线程ID。 

pthread_t pthread_self(void);

此函数可以获取线程自身的ID。

线程终止

如果只需要终止某个线程有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 调用pthread_exit终止自己。
  3. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

void pthread_exit(void*value_ptr);

int pthread_cancel(pthread_t thread);成功返回0,失败返回错误码。

thread:线程ID。

线程等待

进行线程等待所用的函数:

int pthread_join(pthread_t thread,void**value_ptr);成功返回0,失败返回错误码。

thread:线程ID。   value_ptr:它指向一个指针,指针指向线程的返回值。

线程分离

默认情况下,分离的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源。

int pthread_detach(pthread_t thread);可以是线程组内其他线程对目标线程进行分离,也可以是线程自己进行分离。

joinable和分离是冲突的,一个线程不是既是joinable又是分离的。

线程同步与互斥

在很多情况下,很多变量是在线程间共享的,这样的变量成为共享变量,可以通过数据的共享,完成线程之间的交互。

但多个线程之间并发的操作共享变量就会带来一些问题,下面我们利用一个简易的售票系统来展示出这个问题。

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

int ticket = 100;
void *rout(void* arg)
{
	char* id = (char*) arg;
	while(1){
	if(ticket>0)
		{	usleep(1000);
			printf("%s buy a tickte:%d !\n",id,ticket);
			ticket--;
		}
	else
		break;
	}
}
int main()
{
	pthread_t t1,t2,t3,t4;
	pthread_create(&t1,NULL,rout,"thread1");
	pthread_create(&t2,NULL,rout,"thread2");
	pthread_create(&t3,NULL,rout,"thread3");
	pthread_create(&t4,NULL,rout,"thread4");

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_join(t3,NULL);
	pthread_join(t4,NULL);

	return 0;
}

因为我们在函数中将所买票数的编号一并打印出来了,所以如果正常运行的话,应该票数到1截止,但结果是不是这样呢,我们来看看。


通过结果我们能看出来,竟然有负数,这就是共享数据所带来的弊端,在一个线程进行操作的时候,另外一个线程也可能进行相应的操作,从而影响到了结果,为了消除多线程操作的影响,我们就要了解一个东西:mutex(互斥量)。

为了让每次只有一个线程可以进行票的购买行为,我们就要对购买行为增加一把锁。

pthread_mutex_t mutex;声明一个互斥量。

pthread_mutex_init(&mutex,NULL);对互斥量进行初始化。

pthread_mutex_lock(&mutex);上锁。

pthread_mutex_unlock(&mutex);解锁。

pthread_mutex_destroy(&mutex);对互斥量进行销毁。

接下来我们改进一下该售票系统,在运行一下看结果。

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

int ticket = 100;
pthread_mutex_t mutex;
void *rout(void* arg)
{
	char* id = (char*) arg;
	while(1){
			pthread_mutex_lock(&mutex);
	if(ticket>0)
		{	usleep(1000);
			printf("%s buy a tickte:%d !\n",id,ticket);
			ticket--;
			pthread_mutex_unlock(&mutex);
		}
	else
		{
			pthread_mutex_unlock(&mutex);
			break;
		}
	}
}
int main()
{
	pthread_t t1,t2,t3,t4;
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&t1,NULL,rout,"thread1");
	pthread_create(&t2,NULL,rout,"thread2");
	pthread_create(&t3,NULL,rout,"thread3");
	pthread_create(&t4,NULL,rout,"thread4");

	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_join(t3,NULL);
	pthread_join(t4,NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

由更改后的代码与运行结果可知,这次的确符合我们的期望,票数是正确的,因此对于共享数据来说,利用互斥量进行操作是十分重要的。

条件变量

当一个线程互斥的访问某个变量时,它可能在发现在其它线程状态改变前,它什么也做不了,例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加入队列中,这种情况就要用到条件变量了。

pthread_cond_init(&cond,NULL);条件变量的初始化。

pthread_cond_destroy(&cond);条件变量的销毁。

pthread_cond_wait(&cond,&mutex);等待条件满足。

pthread_cond_signal(&cond);唤醒等待。

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

pthread_mutex_t mutex;
pthread_cond_t cond;
void *rout1(void* arg)
{
	while(1)
		{
			pthread_cond_wait(&cond,&mutex);
			printf("activity\n");
		}
}
void *rout2(void* arg)
{
	while(1)
		{
			pthread_cond_signal(&cond);
			sleep(1);
		}	
}
int main()
{
	pthread_t t1,t2;
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);
	pthread_create(&t1,NULL,rout1,NULL);
	pthread_create(&t2,NULL,rout2,NULL);
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	return 0;
}

因为图片不能放动图,所以我们不能很直观的观察到函数运行的结果,但结果是没一秒打印一个activity的,这也证明了条件变量的确起到了相应的作用,在未被唤醒之前,rout1函数并不执行任何动作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值