Linux中的线程
线程的概念
线程是程序执行时的最小单位,即CPU调度和分派的基本单位,一个进程可以由多个线程组成,同一个进程中的多个线程之间共享此进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
线程有四方面特点:
1.线程有独立的堆栈段,共享地址空间,开销较小,切换速度较快。
2.线程间的通信机制比较方便。
3.因为操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。线程使CPU系统更加有效。
4.线程改善了程序结构,避免了一些嵌套循环。
使用pthread_create()函数来创建线程,使用线程的时候有两点注意事项:
1.当多线程访问同一全局变量的时候,一定要加互斥量,也就是上锁。当然最后不要忘记了解锁。
2.正确处理线程结束的问题:因为一个线程的终止,线程的资源不会随线程的终止释放,我们需要调用pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。
线程与进程的区别
地址空间:同进程中的线程共享此进程的地址空间,而进程之间则是独立的地址空间,是隔离的。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。
线程与进程的共同点:两者均可并发执行。
优缺点:
-线程执行开销小,但是不利于资源的管理和保护。
-进程执行开销大,但是能够很好的进行资源管理和保护。
线程基本编程
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread。
例如:g++ -o main filename.cpp -lpthread。
1.线程创建pthread_create()
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()
/* pthread_create.cpp */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread_a(void *argv)
{
printf("I am the thread_a,I get a parameter(thread_id) that is:%ld\n",*(int *)argv);
printf("我自己获取的线程id为:%u\n",(unsigned int)pthread_self());
sleep(3);//测试主线程是否阻塞等待
pthread_exit((char *)"I am the thread_a,I will exit!");
}
void *thread_b(void *argv)
{
printf("I am the thread_b,I get a parameter(thread_id) that is:%ld\n",*(int *)argv);
printf("我自己获取的线程id为:%u\n",(unsigned int)pthread_self());
pthread_exit((char *)"I am the thread_b,I will exit!");
}
int main(int argc, char const *argv[])
{
/* code */
pthread_t tid_a,tid_b;//声明线程id
int ret;
void *tmp1;
void *tmp2;
/* 创建线程,使得该线程执行thread_a函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_a,NULL,thread_a,(void *)&tid_a);
if (ret) {
printf("create thread error\n");
exit(1);
}
printf("tid_a = %u\n",(unsigned int)tid_a);
/* 创建线程,使得该线程执行thread_b函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_b,NULL,thread_b,(void *)&tid_b);
if (ret) {
printf("create thread error\n");
exit(1);
}
printf("tid_b = %u\n",(unsigned int)tid_b);
/* 等待两个进程都退出后,在退出主线程(main) */
pthread_join(tid_a,&tmp1);
pthread_join(tid_b,&tmp2);
printf("从进程获取的数据为:%s-----和-----%s\n",tmp1,tmp2);
return 0;
}
结果:
xzj@xzj-VirtualBox:~/development_test/pthread$ ./pthread_create_main tid_a = 2505316096
tid_b = 2496923392
I am the thread_b,I get a parameter(thread_id) that is:2496923392
我自己获取的线程id为:2496923392
I am the thread_a,I get a parameter(thread_id) that is:2505316096
我自己获取的线程id为:2505316096
从线程获取的数据为:I am the thread_a,I will exit!-----和-----I am the thread_b,I will exit!
2.线程结束pthread_exit()
在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。
3.线程等待pthread_join()
由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
4.线程取消pthread_cancel()
前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。
5.线程标识符获取pthread_self()
获取调用线程的标识ID。
创建线程的时候提到一个函数pthread_self,这个函数使POSIX线程库中的一个函数,通过这个函数可以获得线程的ID,可是我们打印出来这个ID会发现这个ID是一个很大的数字。没有得到我们想象的一个数字,其实这个ID是POSIX线程库提供的一个数字,而linux内核中也为这个线程提供了一个ID,这个ID可以通过gettid获得,gettid是linux内核提供的一个系统调用,Glibc没有封装函数,只能通过系统调用实现。
POSIX:
#include <pthread>
pthread_t pthread_self(void);
linux系统调用:
#include <sys/types.h>
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
6.线程清除pthread_cleanup_push+pthread_cleanup_pop()
线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。
/* pthread_clean.cpp */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
/* 被清除函数 */
void clean_func(void *arg)
{
printf("清除:%s\n",(char *)arg);
//return (void *)0;
}
/* 线程函数1 */
void *thread_func_A(void *arg)
{
printf("我是线程函数A,我将要运行了!\n");
pthread_cleanup_push(clean_func,(void*)"线程函数A第一次操作清除函数");
pthread_cleanup_push(clean_func,(void*)"线程函数A第二次操作清除函数");
if (arg) {
return ((void *)6);
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(0);
return (void *)1;
}
/* 线程函数2 */
void *thread_func_B(void *arg)
{
printf("我是线程函数B,我将要运行了!\n");
pthread_cleanup_push(clean_func,(void*)"线程函数B第一次操作清除函数");
pthread_cleanup_push(clean_func,(void*)"线程函数B第二次操作清除函数");
if (arg) {
return ((void *)7);
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(0);
return (void *)2;
}
int main(int argc, char const *argv[])
{
/* code */
pthread_t tid_a,tid_b;//声明线程id
int ret;
void *res;
ret = pthread_create(&tid_a,NULL,thread_func_A,(void *)1);
if (ret) {
printf("create thread error\n");
exit(1);
}
ret = pthread_create(&tid_b,NULL,thread_func_B,(void *)0);
if (ret) {
printf("create thread error\n");
exit(1);
}
pthread_join(tid_a,&res);
printf("线程tid_a的返回值为:%d\n",(int *)res);
pthread_join(tid_b,&res);
printf("线程tid_b的返回值为:%d\n",(int *)res);
return 0;
结果:
xzj@xzj-VirtualBox:~/development_test/pthread$ ./pthread_clean_main
我是线程函数B,我将要运行了!
清除:线程函数B第二次操作清除函数
我是线程函数A,我将要运行了!
清除:线程函数A第二次操作清除函数
清除:线程函数A第一次操作清除函数
线程tid_a的返回值为:6
线程tid_b的返回值为:2
}
注意:
1.Linux 就是用宏来实现的。这意味着这两个函数必须同时出现,并且属于同一个语法块(同一函数中)。
2. pthread_cleanup_push(clean_func,clean_arg);
......
if(cond)
pthread_cleanup_pop(0);
//在日常编码中很容易犯上面这种错误。因为 pthread_cleanup_push 和 phtread_cleanup_pop 的实现中包
//含了 { 和 } ,所以将 pop 放入 if{} 的代码块中,会导致括号匹配错乱,最终会引发编译错误。
3.可以注册多个清理函数
pthread_cleanup_push(clean_func_1,clean_arg_1)
pthread_cleanup_push(clean_func_2,clean_arg_2)
...
pthread_cleanup_pop(execute_2);
pthread_cleanup_pop(execute_1);
4.从 push 和 pop 的名字可以看出,这是栈的风格,后入先出,就是后注册的清理函数会先执行
其中 pthread_cleanup_pop 的用处是,删除注册的清理函数。如果参数是非 0 值,那么执行一次,再删除清理函数。否则的话,就直接删除清理函数。
线程的属性属性设置
POSIX 线程库定义了线程属性对象 pthread_attr_t ,它封装了线程的创建者能够訪问和改动的线程属性。主要包含例如以下属性:
-
作用域(scope)
-
栈尺寸(stack size)
-
栈地址(stack address)
-
优先级(priority)
-
分离的状态(detached state)
-
调度策略和參数(scheduling policy and parameters)
初始化/销毁线程属性
设置线程的分离属性
在Linux平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。(说明:线程处于joinable状态下)
调用该函数的线程将挂起,等待 th 所表示的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。
如果不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为detached 状态可以通过两种方式来实现。一种是调用pthread_detach() 函数,可以将线程 th 设置为 detached 状态。另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数pthread_create(),这样所创建出来的线程就直接处于 detached 状态。
linux下线程的分离和结合属性
第一种方式:在线程调用函数里使用子进程使用以下操作
pthread_detach(pthread_self());
或者父线程调用时加入:
pthread_detach(thread_id)
第二种方式:就是在创建线程的时候,初始化线程的属性
创建 detach 线程:
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
总之为了在使用 pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。
线程属性的修改
互斥锁线程控制
原因:由于线程共享进程的资源和地址空间,因此在对这些资源进行操作时,必须考虑到线程间资源访问的同步与互斥问题
两种线程同步机制:互斥锁和信号量。
这两个同步机制可以通过互相调用对方来实现,但互斥锁更适用于同时可用的资源是唯一的情况;信号量更适用于同时可用的资源为多个的情况。
1.互斥锁
互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,即上锁和解锁,可以把互斥锁看做某种意义上的全局变量。在同一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会被挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。
互斥锁机制的基本函数如下:
● 互斥锁初始化:pthread_mutex_init()
● 互斥锁上锁:pthread_mutex_lock()
● 互斥锁判断上锁:pthread_mutex_trylock()
● 互斥锁解锁:pthread_mutex_unlock()
● 消除互斥锁:pthread_mutex_destroy()
互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这3种锁的区别主要在于其它未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。
1、快速互斥锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止;
2、递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数
3、检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
默认属性为快速互斥锁。
互斥锁的初始化
互斥锁的操作
2.信号量
信号量就是操作系统中多用到的PV原子操作,它广泛应用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问
PV原子操作主要用于进程或线程间的同步和互斥这两种典型情况。当用于互斥,几个进程(或线程)往往只设置一个信号量sem,其操作流程如图1所示。当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行,其操作流程如图2所示。
Linux实现了Posix的无名信号量,主要用于线程间的互斥与同步。这里主要介绍几个常见函数:
● sem_init()用于创建一个信号量,并初始化它的值。
● sem_wait()和sem_trywait()都相当于P操作,在信号量>0时,它们能将信号量的值减1。两者的区别在于信号量<0时,sem_wait(0将会阻塞进程,而sem_trywait则会立即返回。
● sem_post()相当于V操作,它将信号量的值加1,同时发出信号来唤醒等待的进程。
● sem_getvalue()用于得到信号量的值。
● sem_destroy()用于删除信号量。
信号量的初始化操作
信号量的p、v、销毁等操作
在没有加入任何同步互斥的情况下——线程对资源的操作是随机的
/* pthread_suiji.cpp */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include<time.h>
static int key = 1;//初始化静态变量--线程操作的资源
void *thread_func(void *argv)
{
printf("I am thread_%s,my thread_id is:%u\n",(char *)argv,(unsigned int)pthread_self());
for (int i = 0; i < 5; i++) {
/* code */
printf("start:key=%d in %s\n",key,(char *)argv);
//srand(time(0));
int seconds = rand()%6+1;
sleep(seconds);
key+= seconds;
printf("相加:%d之后,end:key=%d in %s\n",seconds,key,(char *)argv);
}
pthread_exit((char *)argv);
}
int main(int argc, char const *argv[])
{
/* code */
/* code */
pthread_t tid_a,tid_b;//声明线程id
int ret;
void *thread_id1;
void *thread_id2;
/* 创建线程,使得该线程执行thread_a函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_a,NULL,thread_func,(void *)"A");
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 创建线程,使得该线程执行thread_b函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_b,NULL,thread_func,(void *)"B");
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 等待两个进程都退出后,在退出主线程(main) */
pthread_join(tid_a,&thread_id1);
pthread_join(tid_b,&thread_id2);
printf("线程%s已经结束,thread_id:%u-----线程%s已经结束,thread_id:%u\n",(char *)thread_id1,(unsigned int)tid_a,(char *)thread_id2,(unsigned int)tid_b);
return 0;
}
结果:
xzj@xzj-VirtualBox:~/development_test/pthread$ g++ -o pthread_suiji_main pthread_suiji.cpp -lpthread
xzj@xzj-VirtualBox:~/development_test/pthread$ ./pthread_suiji_main
I am thread_B,my thread_id is:4046087936
start:key=1 in B
I am thread_A,my thread_id is:4054480640
start:key=1 in A
相加:2之后,end:key=3 in B
start:key=3 in B
相加:5之后,end:key=8 in A
start:key=8 in A
相加:4之后,end:key=12 in B
start:key=12 in B
相加:2之后,end:key=14 in A
start:key=14 in A
相加:2之后,end:key=16 in A
start:key=16 in A
相加:6之后,end:key=22 in B
start:key=22 in B
相加:1之后,end:key=23 in B
start:key=23 in B
相加:5之后,end:key=28 in A
start:key=28 in A
相加:2之后,end:key=30 in A
相加:4之后,end:key=34 in B
线程A已经结束,thread_id:4054480640-----线程B已经结束,thread_id:4046087936
加入互斥锁--线程之间对同一个资源的操作与占有是互斥的!
作用:实现原本独立且无序的多个线程之间按照顺序执行
/* pthread_suiji.cpp */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include<time.h>
static int key = 1;//初始化静态变量--线程操作的资源
pthread_mutex_t lock;//定义互斥锁
void *thread_func(void *argv)
{
/* 线程上锁 */
int lock_mutex = pthread_mutex_lock(&lock);
if (lock_mutex) {
printf("互斥锁上锁错误");
pthread_exit((char *)argv);
}
printf("I am thread_%s,my thread_id is:%u\n",(char *)argv,(unsigned int)pthread_self());
for (int i = 0; i < 5; i++) {
/* code */
printf("start:key=%d in %s\n",key,(char *)argv);
//srand(time(0));
int seconds = rand()%6+1;
sleep(seconds);
key+= seconds;
printf("相加:%d之后,end:key=%d in %s\n",seconds,key,(char *)argv);
}
pthread_mutex_unlock(&lock);//互斥锁解锁
pthread_exit((char *)argv);
}
int main(int argc, char const *argv[])
{
/* code */
/* code */
pthread_t tid_a,tid_b;//声明线程id
int ret;
void *thread_id1;
void *thread_id2;
/* 初始化互斥锁 */
int init_mutex = pthread_mutex_init(&lock,NULL);//默认是PTHREAD_MUTEX_INITIALIZER 快速互斥锁
if (init_mutex) {
printf("初始化互斥锁失败");
exit(1);
}
/* 创建线程,使得该线程执行thread_a函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_a,NULL,thread_func,(void *)"A");
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 创建线程,使得该线程执行thread_b函数,并且传递一个参数为当前线程的标识符 */
ret = pthread_create(&tid_b,NULL,thread_func,(void *)"B");
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 等待两个进程都退出后,在退出主线程(main) */
pthread_join(tid_a,&thread_id1);
pthread_join(tid_b,&thread_id2);
printf("线程%s已经结束,thread_id:%u-----线程%s已经结束,thread_id:%u\n",(char *)thread_id1,(unsigned int)tid_a,(char *)thread_id2,(unsigned int)tid_b);
pthread_mutex_destroy(&lock);
return 0;
}
结果:
xzj@xzj-VirtualBox:~/development_test/pthread$ g++ -o pthread_suiji_main pthread_suiji.cpp -lpthread
xzj@xzj-VirtualBox:~/development_test/pthread$ ./pthread_suiji_main
I am thread_B,my thread_id is:2425865984
start:key=1 in B
相加:2之后,end:key=3 in B
start:key=3 in B
相加:5之后,end:key=8 in B
start:key=8 in B
相加:4之后,end:key=12 in B
start:key=12 in B
相加:2之后,end:key=14 in B
start:key=14 in B
相加:6之后,end:key=20 in B
I am thread_A,my thread_id is:2434258688
start:key=20 in A
相加:2之后,end:key=22 in A
start:key=22 in A
相加:5之后,end:key=27 in A
start:key=27 in A
相加:1之后,end:key=28 in A
start:key=28 in A
相加:4之后,end:key=32 in A
start:key=32 in A
相加:2之后,end:key=34 in A
线程A已经结束,thread_id:2434258688-----线程B已经结束,thread_id:2425865984
线程之生产者——消费者
/* pthread_semaphore_lock.cpp */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <fcntl.h> //文件的操作
#include <semaphore.h>
#include <sys/ipc.h> //信号量、共享内存、消息队列都会用到这个头文件
#include <errno.h>
#include <sys/types.h>//mkfifo需要此头文件
#include <sys/stat.h>//mkfifo需要此头文件
#define UNIT_SIZE 512 //每个单元的字节大小
#define XZJ_FIFO "fifo"
int fd;//管道描述符
sem_t mutex;//信号量互斥
sem_t empty,full;//信号量同步,empty为缓冲池空的单元,full为缓冲池数据占用的单元,并且:empty+full=1;
/* 生产者线程函数 */
void *producter(void *argv)
{
int write_size;
char write_buffer[UNIT_SIZE] = {0};
while (1)
{
/* 对管道进行循环写 */
sem_wait(&empty);//进行信号量的P操作
sem_wait(&mutex);//进行信号量的P操作
printf("这里是生产者线程:\n");
printf("请输入字符串文本(输入exit表示离开):\n");
gets(write_buffer);
printf("你要将此数据%s写入到管道中!\n",write_buffer);
if (-1 == (write_size = write(fd,write_buffer,UNIT_SIZE))) {
if (errno == EAGAIN) {
printf("the fifo has not been read!please try again!");
}
}else {
printf("我是写线程,write %d into FIFO\n",strlen(write_buffer));
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
printf(" |\n");
}
sem_post(&full);//进行信号量的v操作
sem_post(&mutex);//进行信号量的v操作
if (0 == (strncmp(write_buffer,"exit",4))) {
break;//跳出循环
}
}
pthread_exit(NULL);
}
/* 消费者线程函数 */
void *customer(void *argv)
{
int read_size;
char read_buffer[UNIT_SIZE] = {0};
while (1)
{
/* 对管道进行循环读 */
sem_wait(&full);//进行信号量的P操作
sem_wait(&mutex);//进行信号量的P操作
printf("这里是消费者线程:\n");
memset(read_buffer,0,UNIT_SIZE);//清空数据
if (-1 == (read_size = read(fd,read_buffer,UNIT_SIZE))) {
if (errno == EAGAIN) {
printf("the fifo has no datas!please try again!");
}
}else {
printf("我是读线程,从FIFO读的内容为: %s \n",read_buffer);
printf("*********************************\n");
}
sem_post(&empty);//进行信号量的v操作
sem_post(&mutex);//进行信号量的v操作
if (0 == (strncmp(read_buffer,"exit",4))) {
break;//跳出循环
}
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
/* code */
pthread_t thread_producter_id,thread_customer_id;//生产者与消费者的标识符
int ret;
//char buffer[UNIT_SIZE];
if (-1 == access(XZJ_FIFO,F_OK)) {
//当文件不存在时,创建文件
if ((0 > mkfifo(XZJ_FIFO,0777)) && (errno != EEXIST)) {
printf("create fifo file error\n");
exit(1);
}
}
fd = open(XZJ_FIFO,O_RDWR);//记得close
//以只写只读的方式打开管道
if (-1 == fd) {
printf("open fifo file error");
exit(1);
}
/* 初始化信号量 */
ret = sem_init(&mutex,0,1);//初始化线程之间的互斥量,值为1
ret+= sem_init(&empty,0,1);
ret+= sem_init(&full,0,0);
if (ret) {
printf("信号量初始化错误\n");
exit(1);
}
/* 创建生产者线程,使得该线程执行producter函数 */
ret = pthread_create(&thread_producter_id,NULL,producter,NULL);
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 创建消费者线程,使得该线程执行customer函数 */
ret = pthread_create(&thread_customer_id,NULL,customer,(void *)"B");
if (ret) {
printf("create thread error\n");
exit(1);
}
/* 等待两个进程都退出后,在退出主线程(main) */
pthread_join(thread_producter_id,NULL);
pthread_join(thread_customer_id,NULL);
close(fd);//关闭管道描述符
return 0;
}
线程安全
指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,而且其他的变量的值也和预期的是一样的,不存在执行程序时出现意外结果。(保证数据的一致性)
线程不安全
就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
1、引起线程安全问题的原因:
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2、解决多线程并发访问资源安全问题的方法,实现线程同步的方法:
信号量
互斥锁
读写锁
条件变量
同步的前提:
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
注意:在单线程环境下,没有“线程安全”和“非线程安全”的概念。
死锁是什么?
所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
死锁的必要条件
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
死锁的例子:
1、线程A有X资源,需要Y资源,而线程B有Y资源,需要X资源,A和B都在等待互相释放资源,从而造成了他们之间的资源获取不到又不释放已获取的资源导致死锁。
2、递归死锁:在递归链上面的方法上加锁肯定会出现死锁
在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解除死锁。目前处理死锁的方法可归结为以下四种:
预防死锁
这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
避免死锁
该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。(安全状态、银行家算法)
避免死锁的方法:
加锁顺序
加锁时限
死锁检测
解释三种避免死锁的方法
检测死锁
这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。(死锁定理化简资源分配图)
解除死锁
这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。(资源剥夺法,撤销进程法,进程回退法)
几种同步机制中,各自的优缺点
临界区不是内核对象,只能用于进程内部的线程同步。
互斥、信号量是内核对象可以用于不同进程之间的线程同步。
互斥其实是信号量的一种特殊形式。
互斥可以保证在某一时刻只有一个线程可以拥有临界资源。
信号量可以保证在某一时刻有指定数目的线程可以拥有临界资源。
注意:
1、临界区和互斥量都有“线程所有权”的概念,所以它们是不能用来实现线程间的同步的,只能用来实现互斥。
2、事件和信号量都可以实现线程和进程间的互斥和同步。但是事件和信号量都无法解决遗弃问题。
3、临界区的效率是最高的,因为它不是内核对象。但是临界区不能跨进程使用。
事件,互斥量,信号量都是内核对象,可以跨进程使用,但相应的效率也会低很多。
线程同步常用方式与区别