线程
进程——资源分配的最小单位,线程——程序执行的最小单位
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
使用线程的理由
从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
与线程自身相关API
1.线程创建
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
pthread_t *restrict tidp:长整型无符号类型的指针,简单说就是声明一个 pthread_t t1,这种类型的指针,放在第一个参数的位置,相当于是线程t1的意思
const pthread_attr_t *restrict attr:线程属性,写NULL即可(就是创建默认属性的线程)
void *(*start_rtn)(void *):函数指针,且参数类型为 void* 型--规定了func1(示例会提到这个函数)需要用函数指针的形式写
void *restrict arg:给线程进行传参的参数,类型为(void *)型,且为指针
当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
代码示例:
#include<stdio.h>
#include<pthread.h>//头文件
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
void *func1(void *arg)
{
printf("t1:%ld thread create success \n",(unsigned long)pthread_self());//打印线程id
printf("t1 param is %d\n ",*((int *)arg));
}
int main()
{
int ret;
int param=100;
pthread_t t1;
ret = pthread_create(&t1,NULL,func1,(void*)¶m);
if(ret ==0){
printf("main: craeat success\n");
}
pthread_join(t1,NULL);//保证t1执行完后,主线程在退出,NULL表示不回收退出码
return 0;
}
结果展示:
编译是需要加上 -pthread
1.线程退出
int pthread_exit(void *rval_ptr);
rval_ptr:是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
3.线程等待
int pthread_join(pthread_t thread, void **rval_ptr);
pthread_t thread:哪个线程
void **rval_ptr:返回码
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
代码示例:
#include<stdio.h>
#include<pthread.h>
void *func1(void *arg)
{
static int ret =10;//注意这边不能把static 去掉,去掉会使返回值出现错误,因为下面用于接收的是二级指针
printf("t1:%ld thread create success \n",(unsigned long)pthread_self);
printf("t1 param is %d\n ",*((int *)arg));
pthread_exit((void *)&ret);//将10 给pthread_join(),也可以给个字符或字符串,无所谓的
}
int main()
{
int ret;
int param=100;
int *pret = NULL;
pthread_t t1;
ret = pthread_create(&t1,NULL,func1,(void*)¶m);
if(ret ==0){
printf("main: craeat success\n");
}
printf("main %ld\n",(unsigned long)pthread_self);
pthread_join(t1,(void **)&pret);//(void **)&pret 用于接收退出码,也就是上面的ret
printf("main :ti quit is %d\n",*pret);//将退出码打印出来
return 0;
}
结果展示:
4.线程ID获取及比较
//1.线程id的获取
int pthread_t pthread_self(void);
//2.比较
int pthread_equal(pthread_t tid1, pthread_t tid2);
与互斥锁相关API
互斥锁保证了同一线程的内容会在一起运行,但不能保证线程运行的先后顺序
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
创建及销毁互斥锁API
/*创建锁*/
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t *restrict mutex:创建锁--pthread_mutex_t mutex;mutex为锁的名字,且这个参数为指针
const pthread_mutexattr_t *restrict attr:锁的属性,通常为NULL;
/*销毁锁*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t *mutex:与上面那个一样,同样是一个指针;
加锁及解锁API
/*加锁*/
int pthread_mutex_lock(pthread_mutex_t *mutex);//注意这边是个指针,下面那个文档这个地方有误需要注意
/*解锁*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);//同上
代码示例
#include<stdio.h>
#include<pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
int g_data;
pthread_mutex_t mutex;
void *func1(void *arg)
{
int i;
pthread_mutex_lock(&mutex);
for(i=0;i<5;i++){
printf("t1:%ld thread create success \n",(unsigned long)pthread_self);
printf("t1 param is %d\n ",*((int *)arg));
sleep(1);
}
pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
pthread_mutex_lock(&mutex);
printf("t2:%ld thread create success \n",(unsigned long)pthread_self);
printf("t2 param is %d\n ",*((int *)arg));
pthread_mutex_unlock(&mutex);
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
ret = pthread_create(&t1,NULL,func1,(void*)¶m);
if(ret ==0){
printf("main: craeat success\n");
}
ret = pthread_create(&t2,NULL,func2,(void*)¶m);
if(ret ==0){
printf("main: craeat success\n");
}
printf("main %ld\n",(unsigned long)pthread_self);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
结果示例:
什么时候会造成死锁
与条件变量相关API
- 创建及销毁条件变量
//创建条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t *restrict cond;条件--pthread_cond_t cond;指针调用时要加&符
const pthread_condattr_t *restrict attr:属性,通常为NULL;
//销毁
int pthread_cond_destroy(pthread_cond_t cond);
除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。
- 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
pthread_cond_t *restrict cond:条件,pthread_cond_t cond;这个也是一个指针
pthread_mutex_t *restrict mutex;与上面那个锁是一样的
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);//用的比较少
pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。
3. 触发
int pthread_cond_signal(pthread_cond_t *cond);//这也是个指针,触发这个条件,具体例子见下面的代码示例
int pthread_cond_broadcast(pthread_cond_t *cond);//用的比较少
代码示例:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
int g_data;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *func1(void *arg)
{
static int cnt;
printf("t1:%ld thread create success \n",(unsigned long)pthread_self);
printf("t1 param is %d\n ",*((int *)arg));
while(1){
pthread_cond_wait(&cond,&mutex);
printf("t1 is run =======================\n");
printf("t1=%d\n",g_data);
g_data=0;
sleep(1);
if(cnt++ == 10){
exit(1);
}
}
}
void *func2(void *arg)
{
printf("t2:%ld thread create success \n",(unsigned long)pthread_self);
printf("t2 param is %d\n ",*((int *)arg));
while(1){
printf("t2 : %d\n",g_data);
pthread_mutex_lock(&mutex);
g_data++;
if(g_data==3){
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
ret = pthread_create(&t1,NULL,func1,(void*)¶m);
if(ret ==0){
// printf("main: craeat success\n");
}
ret = pthread_create(&t2,NULL,func2,(void*)¶m);
if(ret ==0){
// printf("main: craeat success\n");
}
// printf("main %ld\n",(unsigned long)pthread_self);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
结果示例:
在这里介绍一个脚本小技巧:
int main(int argc,char**argv)
{
int time = atoi(argv[1]);
int i =0;
for(i=0;i<time;i++){
system("./test");
}
}
执行 ./test 10 >>text.ret.txt &
&地址符表示在后台云运行
将上面那个上面那个程序执行活动结果放到text.ret.txt内
参考优秀博文————https://www.cnblogs.com/xiehongfeng100/p/4620852.html