什么是线程?与进程之间的关系?
进程:是cpu分配资源的最小单位
线程:是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位
进程只是维护程序所需的资源,线程才是真正的执行体.所以一个进程至少包含一个线程.
多线程的好处?
- 在只有一个控制线程下,单线程进程要完成多个任务,多线程下只需要把任务分解,为每个任务分配一个单独的线程,相互独立的任务就可以交叉进行,提高代码的吞吐量
- 多线程自动的访问相同的存储地址空间和文件描述符
一个进程的所有信息对该进程的所有线程都是共享的
每个线程都含有表示执行环境所必须的信息(非共享的 )
1)线程id
4) errno变量
5) 信号屏蔽字
6) 调度优先级
线程标识
进程id在整个系统是唯一的,但线程id不同,只在他所属进程上下文中才有意义.
进程id使用pid-t数据类型表示,是一个非负整数.
线程id是用pthread_t数据类型表示,实现的时候用一个结构来代表pthread_t数据类型,所以可移植操作系统实现不能把他作为整数处理,
用结构表示pthread_t数据类型,我们可以通过pthread_self(获取线程id)pthread_equal(比较是否相等)
#include<stdio.h>
#include <pthread.h>
int main()
{
pthread_t tid;
tid=pthread_self();
printf("tid=%d.....\n",tid);
if(pthread_equal(tid,pthread_self()))
{
printf("equal............\n");
}
else{
printf("no equal......\n");
}
return 0;
}
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址,通常设置为 NULL。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非 0
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* fun(void *arg)
{
pthread_t ntid;
printf("fun:%d\n",pthread_self());
printf("pthread......run......\n");
return (void*)0;
}
int main()
{
pthread_t tid;
pid_t pid;
pthread_create(&tid,NULL,fun,0);
sleep(2);//主线程需要休眠,否则新线程创建不出来就退出了
tid=pthread_self();
printf("main:%d,%d\n",tid,getpid());
return 0;
}
线程终止
任意线程调用exit.和_exit会使整个进程终止
单个线程可以通过3种方式退出(在进程不终止的情况下)
- 从执行函数中退出
- 调用pthread_exit退出线程
- 可以被同一进程其他线程取消
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针。
返回值:无
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功:0
失败:非 0
如果对返回值不感兴趣就将rval_ptr设为null,不获取线程的终止状态
如何让获取已终止程序的状态码
#include<stdio.h>
#include<pthread.h>
W>void* fun1(void*arg)
{
printf("fun1....return ......\n");
return (void*)123;
}
W>void * fun2(void *arg)
{
printf("fun2......exit...........\n");
pthread_exit((void*)2);
}
int main()
{
pthread_t tid1;
pthread_t tid2;
//状态码
void *ret;
//创建线程
pthread_create(&tid1,NULL,fun1,0);
pthread_create(&tid2,NULL,fun2,0);
//等待结束
pthread_join(tid1,&ret);
printf("thread 1 return.....%ld\n",(long)ret);
pthread_join(tid2,&ret);
printf("thread 2 exit.......%ld\n",(long)ret);
return 0;
}
无类型指针参数可以传递包含复杂信息的结构的地址
变量(分配在栈上)作为pthread_exit的参数时出现问题
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
struct data {
int a,b,c,d;
};
void printfdata(char*s,struct data*fd)
{
printf("%s",s);
printf("struct at 0x%lx\n",(long)fd);
printf("fd->a:%d\n",fd->a);
printf("fd->b:%d\n",fd->b);
printf("fd->c:%d\n",fd->c);
printf("fd->d:%d\n",fd->d);
}
void *fun1(void*arg)
{
struct data fd={1,2,3,4};
printfdata("thread 1\n",&fd);
pthread_exit((void*)&fd);
}
void *fun2(void *arg)
{
printf("thread 2:id is %lu\n",pthread_self());
pthread_exit((void*)0);
}
int main()
{
pthread_t tid1;
pthread_t tid2;
struct data *fd;
//创建线程
int ret= pthread_create(&tid1,NULL,fun1,NULL);
if(ret!=0)
{
printf("thread 1 error..........\n");
return 1;
}
ret=pthread_join(tid1,(void *)&fd);
if(ret!=0)
{
printf("join.........error\n");
return 1;
};
sleep(2);
printf("starting second thread.....\n");
pthread_create(&tid2,NULL,fun2,NULL);
sleep(1);
printfdata("second:\n",fd);
return 0;
}
线程可以调用pthread_cancel()函数来请求取消同一进程的其他进程(只是请求并不一定取消)
线程分离(简单说一下)
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,
线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当
被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
thread:线程号。
返回值:
成功:0
失败:非0
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
W>void*fun(void*arg)
{
printf("cccccccccccccccc\n");
pthread_exit((void*)1);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,fun,NULL);
sleep(2);//保证线程创建成功
// 设置分离
pthread_detach(tid);
return 0;
线程同步
两个或两个以上线程在访问同一种资源按先后顺序完成指定任务
如果两个或两个以上线程不同步会出现数据错误
使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
例子
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void *fun1(void*arg)
{
for(int i='A';i<='Z';i++)
{
printf("%c",i);
fflush(stdout);
usleep(100000);
}
pthread_exit((void*)1);
}
W>void*fun2(void*arg)
{
for(int i='a';i<='z';i++)
{
printf("%c",i);
fflush(stdout);
usleep(100000);
}
pthread_exit((void*)2);
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1,NULL,fun1,NULL);
pthread_create(&tid2,NULL,fun2,NULL);
sleep(2);
printf("\n");
pthread_detach(tid1);
pthread_detach(tid2);
return 0;
}
为了避免数据发生错误
对公共资源加锁,在该线程访问时,禁止其他进程使用,完成后解锁
互斥量
pthread_mutex_init函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:
初始化一个互斥锁。
参数:
mutex:互斥锁地址。类型是 pthread_mutex_t 。
attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。
返回值:
成功:0,成功申请的锁默认是打开的。
失败:非 0 错误码
pthread_mutex_lock函数
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
pthread_mutex_destroy函数
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非 0 错误码
pthread_mutex_unlock函数
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功:0
失败:非0错误码
实例:
pthread_mutex_t mutex; //互斥锁
// 打印机
void printer(char *str)
{
pthread_mutex_lock(&mutex); //上锁
while (*str != '\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
pthread_mutex_unlock(&mutex); //解锁
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //打印
}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str); //打印
}
int main(void)
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL); //初始化互斥锁
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}
死锁
1)什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,永远在互相等待
2)死锁引起的原因?
- 竞争不可抢占资源引起死锁
- 竞争可消耗资源引起死锁
- 进程推进顺序不当引起死锁
3)死锁的必要条件
- 互斥条件: 某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完毕后释放资源。
- 请求和保持条件: 程序已经保持了至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,自己占用资源却保持不放。
- 不可抢占条件: 进程已获得的资源没有使用完,不能被抢占。
- 循环等待条件: 必然存在一个循环链。
4)处理死锁的思路
- 预防死锁
破坏死锁的四个必要条件中的一个或多个来预防死锁。
- 避免死锁
和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。
- 检测死锁
运行时出现死锁,能及时发现死锁,把程序解脱出来
- 解除死锁
发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。
5)预防死锁的方法
破坏请求和保持条件
协议1:
所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。
协议2:
允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。
破坏不可抢占条件
当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。
破坏循环等待条件
对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。