Linux线程介绍
进程与线程
一个进程可以有多个线程,同时做多件事情,每个线程各自处理独立的任务。
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令,数据及组织形式的描述,进程才是程序的真正运行实例。
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。进程的所有信息对该进程中的所有线程都是共享的,包括数据段、代码段、堆、栈等。在Unix和类Unix操作系统中,线程也被称为轻量级进程,当轻量级进程更多指的是内核线程,而把用户线程称为线程。
“进程—资源分配的最小单位,线程—程序执行的最小单位。”
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会堆其他进程产生影响,而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,当线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,最好用线程。
使用线程的理由:
理由一:它是一种非常“节俭”的多任务操作方式。新的进程必须分配它独立的地址空间,而一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
理由二:线程间方便的通信机制。对于不同进程来说,他们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,也很不方便。线程则不然,由于同一个进程的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,比如有的变量不能同时被两个线程所修改。
API介绍
一、线程
1、创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数:
thread:pthread_t(长整型)型指针,指向线程ID的指针
attr:线程属性配置,一般用NULL来默认属性配置线程。
start_routine:函数指针,新创建的线程是从该函数的地址开始运行。
arg:无类型指针,可以传基本类型(整数、字符串等)的参数,如果是多个参数可以放在同一个结构体,把该结构体的地址以arg参数传入。
返回:成功返回0,失败返回错误码。
2、线程退出
#include <pthread.h>
void pthread_exit(void *retval);
参数:
retval:无类型指针,使用方法和创建线程中的arg参数相似
3、线程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval_ptr);
参数:
thread:pthread_t(长整型)型指针,指向线程ID的指针
retval_ptr:二级指针
返回:成功返回0,失败返回错误码。
调用该函数时会发生阻塞,直到指定线程调用pthread、从启动例程中返回或者被取消。retval_ptr为二级指针,可以访问到pthread_exit中的一级指针retval的地址。
4、当前线程ID获取
#include <pthread.h>
pthread_t pthread_self(void);
5、线程ID比较
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
返回:若相等则返回非0值,否则返回0
例程:
//文件demo3.c
#include <pthread.h>
#include <stdio.h>
//线程t1执行入口函数
void *func(void* argv)
{
static int ret=30;//pthread_exit函数的参数ret需要定义静态static类型
printf("the thread id is %ld\n",(unsigned long)pthread_self());//打印当前线程ID
printf("argv:%d\n",*((int*)argv));//打印argv参数,需要将(void*)型转为(int*)型
pthread_exit((void*)(&ret));//线程退出,参数取ret的地址,并强转为(void*)型
}
int main()
{
int ret=10;
int* param=NULL;
pthread_t t1;
//创建线程t1,默认配置,并从指针func所指的函数地址开始运行,第三个参数为整型变量ret的地址传入函数func中的argv参数
//ret的地址需要强转为(void *)类型
if(pthread_create(&t1,NULL,func,(void *)&ret)!=0)
{
printf("thread create fail!\n");
}
//打印主线程的线程ID
printf("main thread id is %ld\n",(unsigned long)pthread_self());
//阻塞并等待线程t1结束
//线程t1结束时,会将pthread_exit函数的参数地址传入pthread_join函数的第二个参数
//param为二级指针,需要强转为(void**)类型
if((pthread_join(t1,(void**)(¶m)))!=0)
{
printf("thread join fail!\n");
}
//打印指针param所指的整数
printf("from t1:%d\n",*param);
return 0;
}
实验结果:
Ubuntu@Embed_Learn:~/learn/thread$ gcc demo3.c -lpthread //静态库编译
Ubuntu@Embed_Learn:~/learn/thread$ ./a.out
main thread id is 140455249024768
the thread id is 140455240738560
argv:10
from t1:30
例程:验证一个进程中的多个线程共享数据
//文件demo2.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
int sh_data=0;
void *func1(void* argv)
{
printf("t1 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
while(1)
{
printf("t1:sh_data:%d\n",sh_data++);
sleep(1);
if(sh_data==3)
break;
}
pthread_exit(NULL);
}
void *func2(void* argv)
{
printf("t2 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
while(1)
{
printf("t2:sh_data:%d\n",sh_data++);
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
int ret=10;
char *pstr="this is main thread";
pthread_t t1;
pthread_t t2;
if(pthread_create(&t1,NULL,func1,(void *)pstr)!=0)
{
printf("t1 thread create fail!\n");
}
if(pthread_create(&t2,NULL,func2,(void *)pstr)!=0)
{
printf("t2 thread create fail!\n");
}
printf("main thread id is %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("main:sh_data:%d\n",sh_data++);
sleep(1);
}
if((pthread_join(t1,NULL))!=0)
{
printf("t1 thread join fail!\n");
}
if((pthread_join(t2,NULL))!=0)
{
printf("t2 thread join fail!\n");
}
return 0;
}
实验结果:
Ubuntu@Embed_Learn:~/learn/thread$ gcc demo2.c -lpthread
Ubuntu@Embed_Learn:~/learn/thread$ ./a.out
main thread id is 140269189859072
main:sh_data:0
t1 thread id is 140269181572864
argv:this is main thread
t1:sh_data:1
t2 thread id is 140269173180160
argv:this is main thread
t2:sh_data:2
main:sh_data:3
t1:sh_data:4
t2:sh_data:5
t1:sh_data:6
main:sh_data:7
t2:sh_data:8
...
为什么sh_data为3时,线程t1没有退出呢?
因为多个线程同时抢占着对sh_data进行加1操作,且多个线程对sh_data的操作顺序是随机的。可能sh_data被其他线程抢占操作,使得sh_data在被线程t1读取时就不为3,导致线程不能退出。所以,我们需要用互斥锁来对程序进行优化。
二、互斥锁
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量加锁后,任何试图再次对共享资源加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一次变成可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变成可用,在这种方式下,每次只有一个线程可以向前运行。
1、创建及销毁互斥量
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *restrict mutex);
//参数:
//mutex:互斥量指针
//attr:线程属性初始化,设置为NULL为默认的属性初始化
//返回:若成功返回0,否则返回错误编号
如果互斥量是静态分配的,可以通过常量进行初始化,如下:
pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;
2、加锁及解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *restrict mutex)
int pthread_mutex_unlock(pthread_mutex_t *restrict mutex)
//参数:mutex:互斥量指针
//返回:若成功返回0,否则返回错误编号
例程:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int sh_data=0;
pthread_mutex_t mutex;//定义全局变量互斥锁
void *func1(void* argv)
{
printf("t1 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
pthread_mutex_lock(&mutex);//给互斥量mutex上锁
while(1)
{
printf("t1:sh_data:%d\n",sh_data++);
sleep(1);
if(sh_data==7)
{
printf("t1 thread quit================================\n");
pthread_mutex_unlock(&mutex);//给互斥量mutex解锁
exit(0);
}
}
pthread_exit(NULL);
}
void *func2(void* argv)
{
printf("t2 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
while(1)
{
printf("t2:sh_data:%d\n",sh_data);
pthread_mutex_lock(&mutex);//给互斥量mutex上锁
sh_data++;
pthread_mutex_unlock(&mutex);//给互斥量mutex解锁
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
int ret=10;
char *pstr="this is main thread";
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);//互斥量初始化
if(pthread_create(&t1,NULL,func1,(void *)pstr)!=0)
{
printf("t1 thread create fail!\n");
}
if(pthread_create(&t2,NULL,func2,(void *)pstr)!=0)
{
printf("t2 thread create fail!\n");
}
printf("main thread id is %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("main:sh_data:%d\n",sh_data);
sleep(1);
}
if((pthread_join(t1,NULL))!=0)
{
printf("t1 thread join fail!\n");
}
if((pthread_join(t2,NULL))!=0)
{
printf("t2 thread join fail!\n");
}
pthread_mutex_destroy(&mutex);//互斥量销毁
return 0;
}
实验结果:
Ubuntu@Embed_Learn:~/learn/thread$ gcc demo4.c -lpthread
Ubuntu@Embed_Learn:~/learn/thread$ ./a.out
main thread id is 140388527458048
main:sh_data:0
t2 thread id is 140388510779136
argv:this is main thread
t2:sh_data:0
t1 thread id is 140388519171840
argv:this is main thread
t1:sh_data:1
t1:sh_data:2
main:sh_data:3
t2:sh_data:3
t1:sh_data:3
main:sh_data:4
t1:sh_data:4
main:sh_data:5
main:sh_data:5
t1:sh_data:5
main:sh_data:6
t1:sh_data:6
t1 thread quit================================
不管怎样,t1线程都可以读取到7并正常退出。线程t1和t2在被创建后,可能t2先抢占运行,给互斥量上锁,不过对sh_data加1操作后,会对互斥量解锁,还是会轮到t1运行,并对互斥量上锁,直到sh_data为7时,才对互斥量解锁,并通过exit函数退出进程。
怎么样会造成死锁?
当多个线程需要相同的锁,同时获取,可能会获取到不同的锁,就很容易产生死锁。
比如,线程A和线程B都需要锁1和锁2,如果线程A获取到了锁1,同时线程B获取到了锁2,就会死锁。
三、条件变量
1、创建及销毁条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *restrict cond);
//参数:
//cond:条件变量指针
//attr:条件变量属性初始化,设置为NULL为默认的属性初始化
//返回:若成功返回0,否则返回错误编号
和互斥量的初始化一样,如果条件变量是静态分配的,可以通过常量进行初始化,如下:
pthread_cond_t cond= PTHREAD_COND_INITIALIZER;
2、等待
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
//参数:
//cond:条件变量指针
//mutex:互斥量指针
//返回:若成功返回0,否则返回错误编号
3、触发
#include <pthread.h>
//激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个
int pthread_cond_signal(pthread_cond_t *restrict cond);
//激活所有等待线程
int pthread_cond_broadcast(pthread_cond_t *restrict cond);
//参数:
//cond:条件变量指针
//返回:若成功返回0,否则返回错误编号
例程:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int sh_data=0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *func1(void* argv)
{
static int ret=0;
printf("t1 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("t1 thread start===============%d================\n",ret);
sh_data=0;
sleep(1);
if(ret==10)
{
exit(0);
}
ret++;
}
pthread_exit(NULL);
}
void *func2(void* argv)
{
printf("t2 thread id is %ld\n",(unsigned long)pthread_self());
printf("argv:%s\n",((char*)argv));
while(1)
{
pthread_mutex_lock(&mutex);
sh_data++;
printf("t2 thread:%d\n",sh_data);
if(sh_data==3)
{
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
int ret=10;
char *pstr="this is main thread";
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
if(pthread_create(&t1,NULL,func1,(void *)pstr)!=0)
{
printf("t1 thread create fail!\n");
}
if(pthread_create(&t2,NULL,func2,(void *)pstr)!=0)
{
printf("t2 thread create fail!\n");
}
printf("main thread id is %ld\n",(unsigned long)pthread_self());
if((pthread_join(t1,NULL))!=0)
{
printf("t1 thread join fail!\n");
}
if((pthread_join(t2,NULL))!=0)
{
printf("t2 thread join fail!\n");
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
实验结果:
Ubuntu@Embed_Learn:~/learn/thread$ gcc demo5.c -lpthread -o cond
//静态编译,输出可执行文件文件名cond
Ubuntu@Embed_Learn:~/learn/thread$ ./cond >>ret.txt &
//后台执行cond可执行文件,并将打印结果保存到ret.txt文件。
//参数’&’设置为后台执行
[77]45019
//后台进程的进程号为45019
//文件ret.txt
main thread id is 140036421826304
t1 thread id is 140036413540096
argv:this is main thread
t2 thread id is 140036405147392
argv:this is main thread
t2 thread:1
t2 thread:2
t2 thread:3
t1 thread start===============0================
t2 thread:1
t2 thread:2
t2 thread:3
t1 thread start===============1================
t2 thread:1
t2 thread:2
t2 thread:3
t1 thread start===============2================
t2 thread:1
t2 thread:2
t2 thread:3
t1 thread start===============3================
...
t2 thread:1
t2 thread:2
t2 thread:3
t1 thread start===============10================