Linux系统编程6--线程

进程:有独立的 进程地址空间。有独立的pcb。   分配资源的最小单位。

线程(LWP):有独立的pcb。没有独立的进程地址空间。   最小单位的执行。

ps -Lf 进程号    查看进程的线程

多个进程共同存在时会抢夺cpu,cpu利用时钟中断机制分成多个时间片,若此时有A、B、C三个进程来抢夺的话,A、B、C是站在同等的地位上。若在A里面创建三个线程,在CPU眼里此时A有三个进程,能够抢夺到CPU的概率大大增加,执行速度增加。但若在A中再增加过多的线程,执行速度反而会下降。

线程内核实现原理

类Unix系统中,早期是没有“线程”概念,借助进程机制实现出了线程概念

类Unix系统中,进程与线程的区别与联系

 

从虚拟内存到物理内存,MMU做内存映射时,其实是要借助内核中的pcb的(pcb中包含MMU内存映射的地址)

在pcb中有一个指针,指向一片内存区域,这块区域称为页面,页面里面是一个个的指针,这里面的指针指向的是页表,页表里面也是一个个的指针,指向的是页目录,页目录里面是一个个内存单元,就是内存中的物理地址

当创建一个线程的时候,要复制一份pcb(pcb不完全一样,线程中的pcb也是独立的,但这个指针指向相同),所以线程也有一个指针指向页面,即这样的一个三级页表是一样的(内存地址相同)

而fork出的子进程,有独立的pcb,pcb的指针所指向的页面与父进程的不同,即三级页表不同(有独立的内存地址)

线程共享和非共享

共享非共享
文件描述符表线程ID
每种信号的处理方式处理器现场和栈指针(内核栈)
当前工作目录独立的栈空间(用户空间栈)
用户ID和组IDerrno变量

内存地址空间

(./text./data ./rodataa ./bsss heap/共享库)

信号屏蔽字

调度优先级

独享 栈空间(内核栈、用户栈)

共享 ./text./data ./rodataa ./bsss heap  ---> 共享【全局变量】(errno)

全局变量父子进程间不共享,但线程间共享

线程是库函数实现,不如系统调用稳定

线程可用的情况下,优先选择线程

 创建线程

LWP:线程号(标识线程身份交给CPU,CPU再划分时间轮片,分配程序执行时间),与线程ID概念不同

pthread_t pthread_self(void);	获取线程id。 线程id是在进程地址空间内部,用来标识线程身份的id号。

		返回值:本线程id
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*start_rountn)(void *), void *arg); 创建子线程。

		参1:传出参数,表新创建的子线程 id

		参2:线程属性。传NULL表使用默认属性。

		参3:子线程回调函数。创建成功,pthread_create函数返回时,该函数会被自动调用。
		
		参4:参3的参数。没有的话,传NULL

		返回值:成功:0

			    失败:errno

下面是创建一个子线程去执行任务:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
void sys_err(const char *str){  
    perror(str);  
    exit(1);  
}  
  
void *tfn(void *arg){  //子线程回调函数
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
  
    return NULL;  
}  
  
int main(int argc, char *argv[]){  
//主线程
    pthread_t tid;  
      
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  
    if (ret != 0) {  
        perror("pthread_create error");  
    }  
  
    return 0;  
}  

编译运行,结果如下:

 可以看到,子线程的打印信息并未出现。原因在于,主线程执行完之后,就销毁了整个进程的地址空间,于是子线程就无法打印。简单粗暴的方法就是让主线程睡1秒,等子线程执行。

代码变化如下:

编译执行,如下:

循环创建多个子进程

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
void sys_err(const char *str){  
    perror(str);  
    exit(1);  
}  
  
void *tfn(void *arg){  
    int i = (int)arg;  
    sleep(i);  
    printf("--I'm %dth thread: pid = %d, tid = %lu\n",i+1, getpid(), pthread_self());  
  
    return NULL;  
}  
  
int main(int argc, char *argv[]){  
    int i;  
    int ret;  
    pthread_t tid;  
      
    for(i=0;i<5;i++){  
        ret = pthread_create(&tid, NULL, tfn, (void *)i);  //(void *)i因为回调函数传入是void *arg,要进行强制类型转换
        if (ret != 0) {  
            sys_err("pthread_create error");  
        }  
    }  
    sleep(i);  
    printf("I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());  
  
    return 0;  
}  

编译运行,结果如下:

编译时会出现类型强转的警告,指针4字节转int的8字节,不过不存在精度损失,忽略就行。

 

如果将i取地址后再传入线程创建函数里,就是说

当前传的是:(void *)i

改成:       (void *)&i

相应的,修改回调函数:int i = *((int *)arg)

运行代码,会出现如下结果:

如果多次运行都只有主线程的输出,将主线程等待时长从i改为大于6的数即可。因为子线程等待时间i是不定的,但都小于等于6秒,由于抢cpu时没抢过主线程,导致没有子线程的输出。

错误原因在于,子线程如果用引用传递i,会去读取主线程里的i值,而主线程里的i是动态变化的,不固定。所以,应该采用值传递,不用引用传递。

相关线程函数

pthread_exit函数

void pthread_exit(void *retval);  退出当前线程。

		retval:退出值。 无退出值时,NULL

		exit();	退出当前进程。

		return: 返回到调用者那里去。

		pthread_exit(): 退出当前线程。

 

#include <stdio.h>  

#include <stdlib.h>  

#include <string.h>  

#include <unistd.h>  

#include <errno.h>  

#include <pthread.h>  

  

void sys_err(const char *str){  

    perror(str);  

    exit(1);  

}  

  

void *tfn(void *arg){  

    int i = (int)arg;  

    sleep(i);  

   if (i == 2) {

        exit(0);

     }//退出当前进程。

    printf("--I'm %dth thread: pid = %d, tid = %lu\n",i+1, getpid(), pthread_self());  

  

    return NULL;  

}  

  

int main(int argc, char *argv[]){  

    int i;  

    int ret;  

    pthread_t tid;  

      

    for(i=0;i<5;i++){  

        ret = pthread_create(&tid, NULL, tfn, (void *)i);  //(void *)i因为回调函数传入是void *arg,要进行强制类型转换

        if (ret != 0) {  

            sys_err("pthread_create error");  

        }  

    }  

    sleep(i);  

    printf("I'm main, pid = %d, tid = %lu\n", getpid(), pthread_self());  

  

    return 0;  

}  

一个程序运行有一个主进程,在主进程中创建线程,exit()表示退出进程:

如果在回调函数里加一段代码:

if(i == 2)

exit(0);

看起来好像是退出了第三个子线程,然而运行时,发现后续的4,5也没了。这是因为,exit是退出进程。

一、修改一下,换成:

if(i == 2)

return NULL;

这样运行一下,发现后续线程不会凉凉,说明return是可以达到退出线程的目的。然而真正意义上,return是返回到函数调用者那里去,线程并没有退出。

二、再修改一下,再定义一个函数func,直接返回那种

void *func(void){

    return NULL;

}

if(i == 2)

func();

运行,发现1,2,3,4,5线程都还在,说明没有达到退出目的。

三、再次修改:

void *func(void){

    pthread_exit(NULL);

    return NULL;

}

if(i == 2)

func();

编译运行,发现3没了,看起来很科学的样子。pthread_exit表示将当前线程退出。放在函数里,还是直接调用,都可以。

    回到之前说的一个问题,由于主线程可能先于子线程结束,所以子线程的输出可能不会打印出来,当时是用主线程sleep等待子线程结束来解决的。现在就可以使用pthread_exit来解决了。方法就是将return 0替换为pthread_exit,只退出当前线程,不会对其他线程造成影响

pthread_join函数

int pthread_join(pthread_t thread, void **retval);	阻塞 回收线程。
		thread: 待回收的线程id

		retval:传出参数。 回收的那个线程的退出值。

			线程异常借助,值为 -1。

		返回值:成功:0

			失败:errno

注意这个函数是回收线程资源,不是终止线程(pthread_cancal、pthread_exit、exit())

         与int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*start_rountn)(void *), void *arg);区分开,pthread_create的第一个参数是pthread_t *tid,是一个传出参数

       void pthread_exit(void *retval)的退出值是void *retval,所以pthread_join要将线程,回收的第二个参数是void **retval,也是一个传出参数

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
struct thrd {  
    int var;  
    char str[256];  
};  
  
void sys_err(const char *str)  
{  
    perror(str);  
    exit(1);  
}  
  
void *tfn(void *arg)  //回调函数
{  
    struct thrd *tval;  //结构体指针
    tval = malloc(sizeof(tval));  //使用tval接收malloc分配出的内存地址,但是sizeof(tval)是什么?
sizeof(tval),指的是tral变量占用内存大小,malloc(sizeof(tval))是分配一个tval大小的空间
    tval->var = 100; //指针通过 -> 操作符可以访问成员
    strcpy(tval->str, "hello thread");  

    return (void *)tval;  //子线程的返回值,它会在pthread_join中作为传出参数,retval:传出参数。 回收的那个线程的退出值。
}  
  
int main(int argc, char *argv[])  
{  
    pthread_t tid;  
  
    struct thrd *retval;  
  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  //创建线程
    if (ret != 0)  
        sys_err("pthread_create error");  
  
    //int pthread_join(pthread_t thread, void **retval);  
    ret = pthread_join(tid, (void **)&retval);  //回收线程
    if (ret != 0)  
        sys_err("pthread_join error");  
  
    printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);  
      
    pthread_exit(NULL); //退出主线程 
  
}

编译运行,结果如下:

不可向上面这么写,因为这样的话,tval就是局部变量,返回就是无意义的了(应当使用指针作传出参数)

线程和进程类似,退出后不回收也会产生僵尸,pthread_join可以做回收工作

pthread_cancel函数

int pthread_cancel(pthread_t thread);		杀死一个线程。  需要到达取消点(保存点)

		thread: 待杀死的线程id
		
		返回值:成功:0

			失败:errno

		如果,子线程没有到达取消点, 那么 pthread_cancel 无效。

		我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

		成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
  
void *tfn(void *arg){  
    while (1) {  //子线程每隔一秒打印一次,死循环
        printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
        sleep(1);  
    }  
  
    return NULL;  
}  
  
int main(int argc, char *argv[]){  
    pthread_t tid;  
  
    int ret = pthread_create(&tid, NULL, tfn, NULL);  //创建线程
    if (ret != 0) {  
        fprintf(stderr, "pthread_create error:%s\n", strerror(ret));  
        exit(1);  //线程没有创建成功,退出进程
    }  
  
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  //(主线程)创建成功,打印当前进程和线程id
  
    sleep(5);  
  
    ret = pthread_cancel(tid);          // 终止子线程  
    if (ret != 0) {  
        fprintf(stderr, "pthread_cancel error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    while (1);  //死循环,不让退出主线程
  
    pthread_exit((void *)0);  //退出主线程
}

编译运行,如下:

    可以看到,主线程确实kill了子线程。

这里要注意一点,pthread_cancel工作的必要条件是进入内核,如果tfn真的奇葩到没有进入内核,则pthread_cancel不能杀死线程,此时需要手动设置取消点,就是pthread_testcancel()

线程分离pthread_detach

回收线程资源,主要是打开的文件、线程终止,会自动清理pcb,无需回收、占用的内存资源,以及线程有可能变成僵尸线程,导致无法在创建多余线程

设置线程分离线程终止,会自动清理pcb,无需回收

int pthread_detach(pthread_t thread);		设置线程分离

		thread: 待分离的线程id

	
		返回值:成功:0

			失败:errno	
1.	#include <stdio.h>  
2.	#include <stdlib.h>  
3.	#include <string.h>  
4.	#include <unistd.h>  
5.	#include <errno.h>  
6.	#include <pthread.h>  
7.	  
8.	  
9.	void *tfn(void *arg)  
10.	{  
11.	    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
12.	  
13.	    return NULL;  
14.	}  
15.	  
16.	int main(int argc, char *argv[])  
17.	{  
18.	    pthread_t tid;  
19.	  
20.	    int ret = pthread_create(&tid, NULL, tfn, NULL);  
21.	    if (ret != 0) {  
22.	        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));  
23.	        exit(1);  
24.	    }  
25.	    ret = pthread_detach(tid);              // 设置线程分离` 线程终止,会自动清理pcb,无需回收  
26.	    if (ret != 0) {  
27.	        fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));  
28.	        exit(1);  
29.	    }  
30.	  
31.	    sleep(1);  
32.	  
33.	    ret = pthread_join(tid, NULL);  
34.	    printf("join ret = %d\n", ret);  
35.	    if (ret != 0) {  
36.	        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));  
37.	        exit(1);  
38.	    }  
39.	  
40.	    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
41.	  
42.	    pthread_exit((void *)0);  
43.	}  

编译运行,结果如下:

因为线程分离后,系统会自动回收资源,用pthread_join去回收已经被系统回收的线程,那个线程号就是无效参数

进程和线程函数对比

线程进程
pthread_create()fork()
pthread_self()getpid()
pthread_exit()exit();       
pthread_join()wait()/waitpid()
pthread_cancel()kill()
pthread_detach()

线程属性设置分离线程

设置分离属性。

    pthread_attr_t attr      创建一个线程属性结构体变量(基本传null

    pthread_attr_init(&attr);   初始化线程属性

    pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);      设置线程属性为 分离态

    pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

    pthread_attr_destroy(&attr);    销毁线程属性

调整线程状态,使线程创建出来就是分离态,代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <pthread.h>  
  
  
void *tfn(void *arg)  
{  
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
  
    return NULL;  
}  
  
int main(int argc, char *argv[])  
{  
    pthread_t tid;  
  
    pthread_attr_t attr;  
  
    int ret = pthread_attr_init(&attr);  
    if (ret != 0) {  
        fprintf(stderr, "attr_init error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);      // 设置线程属性为 分离属性  
    if (ret != 0) {  
        fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    ret = pthread_create(&tid, &attr, tfn, NULL);  
    if (ret != 0) {  
        perror("pthread_create error");  
    }  
  
    ret = pthread_attr_destroy(&attr);  
    if (ret != 0) {  
        fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    ret = pthread_join(tid, NULL);  
    if (ret != 0) {  
        fprintf(stderr, "pthread_join error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
  
    pthread_exit((void *)0);  
}

编译运行,结果如下:

如图,pthread_join报错,说明线程已经自动回收,设置分离成功。

 线程使用注意事项

 第三个是因为线程间共享堆区

第五个,当有多个线程时,来了一个信号,这些线程是谁抢到这个信号谁就处理这个信号。若要注定某一个线程处理,每个线程有属于自己独立的屏蔽字,但其共享信号的处理方式,未决信号集共享

 线程同步

协同步调,对公共区域数据按序访问。防止数据混乱,产生与时间有关的错误

数据混乱的原因:

  1. 资源共享(独享资源则不会)
  2. 调度随机(意味着数据访问会出现竞争)
  3. 线程间缺乏必要同步机制

互斥锁

锁的使用:

    建议锁!对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但,锁本身不具备强制性。

主要应用函数:
	pthread_mutex_init		初始化函数
	pthread_mutex_destory	销毁函数
	pthread_mutex_lock		加锁函数
	pthread_mutex_trylock	尝试拿锁函数
	pthread_mutex_unlock		解锁函数

以上5个函数的返回值都是:成功返回0,失败返回错误号
pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待
pthread_mutex_t mutex;变量mutex只有两种取值:0,1

 

使用mutex(互斥量、互斥锁)一般步骤:

    pthread_mutex_t 类型。

    1. pthread_mutex_t lock;  创建锁

    2  pthread_mutex_init; 初始化      1

    3. pthread_mutex_lock;加锁      1-- --> 0

    4. 访问共享数据(stdout)   

    5. pthrad_mutext_unlock();解锁     0++ --> 1

    6. pthead_mutex_destroy;销毁锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
const pthread_mutexattr_t *restrict attr)
这里的restrict关键字,表示指针指向的内容只能通过这个指针进行修改
restrict关键字:

	用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。
初始化互斥量:

		pthread_mutex_t mutex;

		1. pthread_mutex_init(&mutex, NULL);   			动态初始化。

		2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;	静态初始化。

使用锁实现互斥访问共享区:

#include <stdio.h>  
#include <string.h>  
#include <pthread.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
pthread_mutex_t mutex;      // 定义一把互斥锁  (全局锁)都可访问
  
void *tfn(void *arg)  
{  
    srand(time(NULL));  
  
    while (1) {  
        pthread_mutex_lock(&mutex);     // 加锁  
        printf("hello ");  
        sleep(rand() % 3);  // 模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误  
        printf("world\n");  
        pthread_mutex_unlock(&mutex);   // 解锁  
        sleep(rand() % 3);  
    }  
  
    return NULL;  
}  
  
int main(void)  
{  
    pthread_t tid;  
    srand(time(NULL));  
    int ret = pthread_mutex_init(&mutex, NULL);    // 初始化互斥锁  ,创建线程前
    if(ret != 0){  
        fprintf(stderr, "mutex init error:%s\n", strerror(ret));  
        exit(1);  
    }  
  
    pthread_create(&tid, NULL, tfn, NULL);  
    while (1) {  
        pthread_mutex_lock(&mutex);     // 加锁  
        printf("HELLO ");  
        sleep(rand() % 3);  
        printf("WORLD\n");  
        pthread_mutex_unlock(&mutex);   // 解锁  
        sleep(rand() % 3);  
    }  
    pthread_join(tid, NULL);  
      
    pthread_mutex_destory(&mutex);     // 销毁互斥锁  
  
    return 0;  
}  

编译运行,结果如下:

可以看到,主线程和子线程在访问共享区时就没有交叉输出的情况了。

互斥锁使用技巧

注意事项:

       尽量保证锁的粒度, 越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)

 

像这个就是访问结束后没有立即解锁,导致产生竞争CPU,这种情况下一般都是此线程能够竞争成功。

       互斥锁,本质是结构体。 我们可以看成整数。 初值为 1。(pthread_mutex_init() 函数调用成功。)

       加锁: --操作, 阻塞线程。

       解锁: ++操作, 唤醒阻塞在锁上的线程。

       try锁:尝试加锁,成功--。失败,返回。同时设置错误号 EBUSY

读写锁

 读写锁:

    锁只有一把。以读方式给数据加锁——读锁。以写方式给数据加锁——写锁。

    读共享,写独占。

    写锁优先级高。

    相较于互斥量而言,当读线程多的时候,提高访问效率

pthread_rwlock_t  rwlock;

	pthread_rwlock_init(&rwlock, NULL);

	pthread_rwlock_rdlock(&rwlock);		try

	pthread_rwlock_wrlock(&rwlock);		try

	pthread_rwlock_unlock(&rwlock);

	pthread_rwlock_destroy(&rwlock);

以上函数都是成功返回0,失败返回错误号。
pthread_rwlock_t 类型	用于定义一个读写锁变量
pthread_rwlock_t	rwlock

死锁

是使用锁不恰当导致的现象:

       1. 对一个锁反复lock。

       2. 两个线程,各自持有一把锁,请求另一把。

               线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

 

  
#include <stdio.h>  
#include <unistd.h>  
#include <pthread.h>  
  
int counter;                          //全局资源  
pthread_rwlock_t rwlock;  //全局变量
  
void *th_write(void *arg)  
{  
    int t;  
    int i = (int)arg;  
  
    while (1) {  
        t = counter;                    // 保存写之前的值  
        usleep(1000);  
  
        pthread_rwlock_wrlock(&rwlock);  
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);  
        pthread_rwlock_unlock(&rwlock);  
  
        usleep(9000);               // 给 r 锁提供机会  
    }  
    return NULL;  
}  
  
void *th_read(void *arg)  
{  
    int i = (int)arg;  
  
    while (1) {  
        pthread_rwlock_rdlock(&rwlock);  
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);  
        pthread_rwlock_unlock(&rwlock);  
  
        usleep(2000);                // 给写锁提供机会  
    }  
    return NULL;  
}  
  
int main(void)  
{  
    int i;  
    pthread_t tid[8];  
  
    pthread_rwlock_init(&rwlock, NULL);  
  
    for (i = 0; i < 3; i++)  //创建三个线程用来写
        pthread_create(&tid[i], NULL, th_write, (void *)i);  
  
    for (i = 0; i < 5; i++)  //创建五个线程用来读
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);  
  
    for (i = 0; i < 8; i++)  //回收八个线程
        pthread_join(tid[i], NULL);  
  
    pthread_rwlock_destroy(&rwlock);            //释放读写琐  
  
    return 0;  
}  

编译运行,结果如下:

程序输出飞快,随便截个图,如上图。

由于读共享,写独占。写锁优先级高。前面5个read一定先于后面的write到达的,不然write会抢到锁先进行写操作。

 静态初始化条件变量和互斥量

条件变量:

    本身不是锁!  但是通常结合mutex锁来使用。

主要应用函数:

pthread_cond_t cond;

    初始化条件变量:

       1. pthread_cond_init(&cond, NULL);            动态初始化。

       2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;   静态初始化

条件变量和相关函数wait

阻塞等待条件:

       pthread_cond_wait(&cond, &mutex);

       作用:

1) 阻塞等待条件变量满足 

2) 解锁已经加锁成功的信号量 (相当于 pthread_mutex_unlock(&mutex),所以在调用这个函数之前要进行加锁操作),12两步为一个原子操作(12一步完成)

3)  当条件满足,函数返回时,解除阻塞并重新申请获取互斥锁。重新加锁信号量 (相当于, pthread_mutex_lock(&mutex);)

Pthread_cond_signl()        唤醒阻塞在条件变量中的一个线程

Pthread_cond_broadcast()    唤醒阻塞在条件变量中的多个线程

Pthread_cond_timewait()     等待一定时间,一旦超过这个时间就不再等待

条件变量的生产者消费者

 

1、在全局区域定义一个结构体,结构体是一个链表

2、在全局区域创建一个链表的头指针

3、在全局区域创建一个条件变量(大师傅是否生产出饼)、一个互斥量

4、消费者(线程)

      定义一个节点(用以拿饼)

      加锁(不让其他线程访问)

      判断是否有饼,没饼的话解锁(让其他线程访问)、阻塞等待

5、生产者(线程)

      创建链表节点(生产产品,随机生成1--1000)

      加锁

      把饼放到公共区

      解锁

      通知阻塞在条件变量上的线程

6、消费者(线程)

      阻塞被唤醒,加锁

      把饼从公共区拿出

      解锁

      Free掉这个饼

信号量概念及其相关操作函数

信号量:

    应用于线程、进程间同步。

    相当于 初始化值为 N 的互斥量。  N值,表示可以同时访问共享数据区的线程数。


   

 函数:

       sem_t sem; 定义类型。

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       参数:

           sem: 信号量

           pshared:  0: 用于线程间同步

                           1: 用于进程间同步

           value:N值。(指定同时访问的线程数)


       sem_destroy();

       sem_wait();       一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。 (对比 pthread_mutex_lock)

       sem_post();       一次调用,做一次++ 操作. 当信号量的值为 N 时, 再次 ++ 就会阻塞。(对比 pthread_mutex_unlock)

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值