线程
- 线程是一个进程内部的控制序列,所有进程都至少存在一个执行进程。
进程和线程的区别
- 进程是资源竞争的基本单位。
- 线程是程序执行的最小单位。
- 线程共享进程数据,但也拥有自己的一部分数据。
每个进程可以拥有多个线程,但至少必须有一个线程。
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多。
- 与进程之间的切换相比,线程之间的切换比较容易。
- 线程占用的资源要比进程少很多。
- 能充分利用多处理器的可并行数量。
- 在等待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。
线程终止
如果只需要终止某个线程有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 调用pthread_exit终止自己。
- 一个线程可以调用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函数并不执行任何动作。