06-系统编程(线程的属性)

线程的属性




一、线程的属性----分离属性

1.分离属性

首先分离属性是线程的一个属性,有了分离属性的线程,不需要别的线程去接合自己的(回收自己的资源)。
但是虽然说是分离的,但是进程退出了,该线程还是会退出的。
设置了分离属性的线程 -> 不需要pthread_join()
设置了非分离属性的线程 -> 需要pthread_join() -> 默认创建的普通属性线程就是非分离线程

2.创建分离属性的线程

pthread_attr_setdetachstate()
方法一:添加一个分离属性到一个属性变量中,然后使用属性变量去创建一个线程,那么创建出来的线程就是具有分离属性的线程
1)定义一个属性变量 -> 数据类型:pthread_attr_t
2)初始化属性变量 -> pthread_attr_init()

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
参数:
    attr:未初始化的属性变量
返回值:
    成功:0
    失败:非0错误码

3)设置分离属性到属性变量中//set(设置)detach(分离)state(状态)

#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数:
    attr:已经初始化过的属性变量
    detachstate:
        PTHREAD_CREATE_DETACHED  -> 分离属性
        PTHREAD_CREATE_JOINABLE  -> 非分离属性(默认状态)
返回值:
    成功:0
    失败:非0错误码

4)使用属性变量去创建一个新的线程

pthread_create(&tid,&attr,.....);   -> 创建出来的线程就是分离属性的线程,不需要pthread_join()

5)销毁属性变量 -> pthread_attr_destroy()

int pthread_attr_destroy(pthread_attr_t *attr);
参数:
    attr:已经初始化过的属性变量
返回值:
    成功:0
    失败:非0错误码

代码说明:

#include<stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

//方法1 : 如何创建具有分离属性的线程

void* start_routine(void *arg)
{
    printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
    sleep(3);
}

int main()
{
    //1、定义一个线程属性变量
    pthread_attr_t attr;

    //2、初始化清空属性变量
    pthread_attr_init(&attr);

    //3、把分离属性加入到属性变量中
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

    //4、创建一条具有分离属性的子线程
    pthread_t thread;
    pthread_create(&thread,&attr,start_routine,NULL);

    //5、销毁属性变量
    pthread_attr_destroy(&attr);

    pause();//暂停自己
}

方法二:先创建一个普通线程,然后在线程中调用一个设置分离属性的函数,那么这个线程就变成分离的属性
1)设置线程本身的属性为分离属性 -> pthread_detach()

#include <pthread.h>
int pthread_detach(pthread_t thread);
函数作用:
    设置分离属性给线程
参数:
    thread:需要设置分离属性的线程的ID号
返回值:
    成功:0
    失败:非0错误码

2)获取线程的ID号-> pthread_self()

#include <pthread.h>
pthread_t pthread_self(void);
参数:
    无
返回值:线程的ID号。
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>

void* start_routine(void *arg)
{
    //在子线程的内部 设置 分离属性
    pthread_detach(pthread_self());
    printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
}

int main()
{
    //4、创建一条具有分离属性的子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    //pause();
}

代码说明:

#include<stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

//方法2:如何创建具有分离属性的线程
void* start_routine(void *arg)
{
    //在子线程的内部设置分离属性
    pthread_detach(pthread_self());
    printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
}

int main()
{
    //4、创建一条具有分离属性的子线程
    pthread_t thread;
    int cnt=0;

    while(1)
    {
        int ret= pthread_create(&thread,NULL,start_routine,NULL);
        if(ret != 0){
            printf("pthread_create error\n");
            break;
        }
        printf("cnt:%d\n",cnt++);
    }
}

总结:
无论是否添加了分离属性的线程,理论上创建的线程的数量没有限制。
用两种方法设置分离属性的子线程,主线程都无法结合成功

二、线程的取消

1.一般主线程不用于去处理任务,只是控制子线程状态,例如取消,接合… -> pthread_cancel()

主线程 -> 取消请求 -> 子线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);
函数作用:
    发送一个取消请求给子线程。
参数:
    thread:需要取消的线程的ID号。
返回值:
    成功:0
    失败:非0错误码
注意:线程收到取消请求,就等价于提前退出。
pthread_exit()  -> 是线程主动退出 ->退出值-> pthread_join()
主线程给子线程发送取消请求--->子线程收到取消请求-> 线程被迫退出(被动退出) -> 没有退出值

例子:使用pthread_cancel去取消一个线程

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
int pthread_status = 10;

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
    int cnt=30;
    while(cnt--)
    {
        sleep(1);
        printf("[%lu]start_routine cnt:%d\n",pthread_self(),cnt); //pthread_self 打印自己的线程ID号
    }

    pthread_exit(&pthread_status);
} 

int main()
{
    //创建一条子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    sleep(5);
    //给子线程发送一个取消请求
    pthread_cancel(thread);
    
    //等待子线程退出
    void *p = NULL;
    pthread_join(thread,&p); // p = (void*)&pthread_status
    //如果此处对p进行解引用就会产生段错误
    return 0;
现象说明:
    取消线程之后,主线程是无法接受它的返回值
    p没有被函数pthread_join分配空间,它还是NULL指针,对空指针进行解引用就段错误
如果子线程没有分离属性,主线程是可以结合成功,但是没有返回值

2.设置线程响应取消的状态-> pthread_setcancelstate()

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
参数:
state:
PTHREAD_CANCEL_ENABLE -> 能响应 -> 线程默认属性
-> 是马上响应,还是延迟响应 -> 取决于type。
PTHREAD_CANCEL_DISABLE -> 不能响应
oldstate:保留之前的状态,如果不关心,则填NULL。
返回值:
成功:0
失败:非0错误码。
说明:
pthread_setcancelstate一般使用在子线程中
If a thread has disabled cancellation, then a cancellation request remains queued until the thread enables cancellation.
//如果一个线程不能响应取消的,那么在这个过程中收到了取消请求,那么这个请求会直到这个线程能响应取消请求为止才会被响应。(主线程发送的取消命令没有被丢弃,只是挂起)

If a cancellation request is received, it is blocked until cancelability is enabled.
//如果收到取消请求,那么就会阻塞到这个线程能响应为止

代码说明:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>

int pthread_status = 20;

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
    //子线程设置不响应取消请求 
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    
    int cnt=0;
    while(1)
    {
        sleep(1);
        printf("start_routine:%lu cnt:%d\n",pthread_self(),cnt++); //pthread_self 打印自己的线程ID号
        
        if(cnt == 10)
        {
            //子线程设置 取消请求能响应 
            pthread_setcancelstate(PTHREAD_CANCEL_ENABLE  , NULL);
        }
    }
} 

int main()
{
    //创建一条子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    sleep(2);
    //给子线程发送一个取消请求
    pthread_cancel(thread);

    //等待子线程退出
    pthread_join(thread,NULL);
    
    return 0;
现象说明:
    刚开始子线程不能接收取消请求,10秒之后才能取消

3.设置线程响应取消的类型-> pthread_setcanceltype()

#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
参数:
type:
PTHREAD_CANCEL_DEFERRED -> 延迟取消 -> 遇到一个取消点函数才会响应取消-> 线程默认属性
PTHREAD_CANCEL_ASYNCHRONOUS -> 立即响应
oldtype:保存之前的状态,如果不关心,则填NULL。

注意: 线程是遇到取消点函数之后才会响应取消的
取消点函数有哪些呢? -> man 7 pthreads
fprintf()
fputc()
fputs()
sleep()
printf()
usleep()

例子1:线程延迟取消,遇到取消点才会取消

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{   
    while(1)
    {
        //printf("start_routine\n");//取消点函数
        //sleep(1); //取消点函数
    }
} 

int main()
{
    //创建一条子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    //给子线程发送一个取消请求
    pthread_cancel(thread);

    //等待子线程退出
    pthread_join(thread,NULL);
    
    return 0;
}
现象说明:主线程给子线程 发送线程取消 ,但是子线程在执行过程中没有遇到取消点函数,所以不能响应取消请求

例子2:给子线程设置立即响应取消请求,收到主线程发来的取消请求,立即响应

#include<stdio.h>
#include<pthread.h>

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{   
    //当线程收到取消请求之后,立即响应,不需要遇到取消点
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
    while(1)
    {
        //printf("start_routine\n");//取消点函数
        //sleep(1); //取消点函数
    }
} 

int main()
{
    //创建一条子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    
    //给子线程发送一个取消请求
    pthread_cancel(thread);

    //等待子线程退出
    pthread_join(thread,NULL);
    
    return 0;
}
现象说明:子线程收到主线程的取消请求后,立即响应退出线程

三、线程取消例程函数

1.线程取消例程函数

当线程收到取消请求时,先不要马上响应取消请求,而是执行一个例程函数先,执行完这个函数再响应取消。(类似于进程里面的signal–>函数句柄)

2.使用取消例程函数

为了防止线程带着一些公共资源而被取消掉,如果带着资源来退出,那么其他线程无法再次使用该资源

3.实现

1)压栈线程的取消例程函数-> pthread_cleanup_push()
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数:
routine: 线程的取消例程函数 -> 必须是: void fun(void *arg)
arg:传递给取消例程函数的参数

2)弹栈线程的取消例程函数 --> pthread_cleanup_pop()
#include <pthread.h>
void pthread_cleanup_pop(int execute);
参数:
execute:
0 -> 删除时,直接删除。
非0 -> 删除时,会先执行一遍例程函数,再删除。
模型:
void* fun(void *arg)
{
pthread_cleanup_push(xxxx); -> 将来收到取消请求,就会执行xxxx这个函数。
…;
…; <-- 收到取消请求
…;

pthread_cleanup_pop(0);

}
注意(取消例程函数的三要素):

1.子线程收到取消请求之后,(遇到取消点函数)就会执行线程取消例程函数,然后执行完就响应取消请求直接退出,不会再往下面执行了 。
2.如果子线程没有收到取消请求,而且程序执行到pthread_cleanup_pop该函数时,此函数才会执行,
并且根据参数决定是否执行线程取消例程函数再退出子线程。
3.这两个函数都必须是成对出现的,如果只写一个直接编译会报错

代码说明:

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void routine(void *arg)
{
    printf("线程取消例程\n");
}

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
    //压栈
    pthread_cleanup_push(routine,NULL);
    while(1)
    {
        printf("start_routine\n");
        sleep(1);
    }
    //弹栈
    pthread_cleanup_pop(0);//0不执行 非0才执行
} 

int main()
{
    int ret=0;
    //创建一条子线程
    pthread_t thread;
    pthread_create(&thread,NULL,start_routine,NULL);
    if(ret != 0)
    {
        perror("pthread_create fail");
        return -1;
    }

    sleep(5);
    //给子线程发送一个取消请求
    pthread_cancel(thread);

    //等待子线程退出
    pthread_join(thread,NULL);
    
    return 0;
}
现象说明:子线程收到取消请求之后,立即执行取消例程函数。pop后面的函数不执行

四、多条线程同时访问某一片内存空间导致数据践踏

#include<stdio.h>
#include<pthread.h>
#include <unistd.h>

//全局变量-->共享变量
int g_val = 0;

//线程例程,创建一条线程之后,去执行这个函数
void* start_routine1(void *arg)
{
    g_val = 100;
    sleep(2);//延时2秒,此时线程2执行g_val = 200; 该变量的值已经被线程2改变了
    
    printf("start_routine1 g_val:%d\n",g_val);
    
    pthread_exit(NULL);
} 
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine2(void *arg)
{
    sleep(1);//延时1秒,此时 线程1 执行 g_val = 100;
    g_val = 200;

    printf("start_routine2 g_val:%d\n",g_val);
    
    pthread_exit(NULL);
} 
int main()
{
    //创建一条子线程
    pthread_t thread1;
    pthread_create(&thread1,NULL,start_routine1,NULL);
    //创建一条子线程
    pthread_t thread2;
    pthread_create(&thread2,NULL,start_routine2,NULL);
    
    //等待子线程退出
    pthread_join(thread1,NULL); 
    
    return 0;
}
现象说明:
start_routine2 g_val:200
start_routine1 g_val:200

结论:线程1和线程2同时访问某一片内存空间,导致数据发生践踏。
如何去避免呢?? 也就是线程1在访问这片内存空间时,别的线程不能进来。—使用同步互斥机制
(各个线程操作数据的时候没有保护机制,很容易自己的数据被其它的线程篡改,很危险!!!)

线程同步互斥方式

1.同步互斥

同步互斥就是使得线程处理任务时有先后的顺序,为了防止线程资源(共享资源)被抢占的问题

2.、处理同步互斥方式

信号量 -> 进程 (共享内存+信号量一起使用) —进程IPC中的一种
有名信号量 -> 进程 (共享内存+有名信号量一起使用) <编译的时候需要链接线程库-lpthread>
无名信号量 -> 线程
互斥锁 -> 线程
读写锁 -> 线程
条件变量 -> 线程(互斥锁+条件变量一起使用)

六、同步互斥方式 – 有名信号量

1.有名信号量的特点

有名信号量与信号量非常相似,但是信号量的值只能是0/1,而是有名信号量可以0~正无穷。
信号量使用空间 + 数据来处理互斥,而有名信号量只是使用数据来处理。(一般是作用于进程之间)

2.有名信号量的函数接口

1)创建并打开一个有名信号量 -> sem_open()

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
函数作用:
    初始化并且打开一个有名信号量
参数:
    name: 有名信号量的名字,要求必须以"/"开头,例如"/sem_test"    -> 存在于/dev/shm目录下
    oflag:
        O_CREAT  -> 如果不存在创建
        O_CREAT|O_EXCL  -> 不存在则创建  -> 存在就报错
    mode:八进制权限,例如: 0777
    value:有名信号量的起始值
注意:
    如果oflag中有O_CREAT这个选项,则这个mode与value必须要填。
    如果有名信号量已经存在的了,但是你又写了O_CREAT,那么后面你填的mode和value就会被忽略。
返回值:
    成功:有名信号量的地址
    失败:SEM_FAILED   -> NULL / (sem_t *)-1

2)有名信号量的P操作

P操作: 资源数-1操作    -> sem_wait()  -> man 3 sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem);  -> 如果减不了1,这个函数就会阻塞
参数:
    sem:有名信号量的地址
返回值:
    成功:0    -> 资源数可以-1
    失败:-1
比如:
如果当前的值为2,那么sem_wait()就会马上返回,并且将值设置为1。
如果当前的值为1,那么sem_wait()就会马上返回,并且将值设置为0。
如果当前的值为0,那么sem_wait()就会阻塞,一直阻塞到有名信号量的值不为0为止。

3)有名信号量的V操作

V操作: 资源数+1操作   -> sem_post()  -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
    sem:有名信号量的地址
返回值:
    成功:0    -> 资源数可以+1
    失败:-1

4)关闭有名信号量。 -> sem_close()

#include <semaphore.h>
int sem_close(sem_t *sem);
参数:
    sem:有名信号量的地址
返回值:
    成功:0
    失败:-1

5)删除有名信号量。 -> sem_unlink()

#include <semaphore.h>
int sem_unlink(const char *name);
参数:
    name: 有名信号量的名字
返回值:
    成功:0
    失败:-1

3.有名信号量与信号量的比较

不同点:
1)信号量函数复杂,有名信号量函数简单。
2)信号量有空间和数据两个需要操作;有名信号量只有数据一个需要操作。
3)信号量编译的时候不需要线程库;有名信号量编译的时候需要链接线程库。
相同点:
1)都是作用于进程之间的通信
2)都有P/V操作;P是减1操作,V是加1操作

代码说明:
进程1:

#include<stdio.h>
#include<string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

#define SEM_NAME    "/semname1"

int main()
{
    //1、获取key值
    key_t key = ftok(".",10);
    //2、根据key值 获取共享内存的ID号
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    char*shm_p = shmat(shmid,NULL,0);
    if(shm_p == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }
    
    //4、创建并打开一个有名信号量
    sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0); 
    if(nameSem == SEM_FAILED )
    {
        printf("sem_open error\n");
        return -1;
    }
    
    //此时映射出来的shm_p 就是两个进程的共享内存
    while(1)
    {   
        //从键盘上获取数据,存储到共享内存shm_p
        scanf("%s",shm_p);
    
        //有名信号量的V操作  数据 +1
        sem_post(nameSem);
        
        //退出条件,这里要注意 应该使用strncmp 指定字节数
        if(strncmp(shm_p,"exit",4) == 0)
            break;
    }
    //4、当不再使用时,解除映射关系
    shmdt(shm_p);
    //5、当没有进程再需要使用这块共享内存时,删除它
    shmctl(shmid,  IPC_RMID, NULL);
    //6、关闭有名信号量。
    sem_close(nameSem);
    //7、删除有名信号量
    sem_unlink(SEM_NAME);
    
    return 0;
}

进程2:

#include<stdio.h>
#include<string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

#define SEM_NAME    "/semname1"

int main()
{
    //1、获取key值
    key_t key = ftok(".",10);
    //2、根据key值 获取共享内存的ID号
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    char*shm_p = shmat(shmid,NULL,0);
    if(shm_p == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }
    //4、创建并打开一个有名信号量
    sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0); 
    if(nameSem == SEM_FAILED )
    {
        printf("sem_open error\n");
        return -1;
    }
    
    //此时映射出来的shm_p 就是两个进程的共享内存
    while(1)
    {
        //有名信号量的P操作 数据 -1 
        sem_wait(nameSem); 
        
        //从共享内存中读取数据
        printf("recv:%s\n",shm_p);
        
        //退出条件,这里要注意 应该使用strncmp 指定字节数
        if(strncmp(shm_p,"exit",4) == 0)
            break;
    }

    return 0;

同步互斥方式 — 无名信号量

无名信号量

一般作用于线程之间的互斥,由于是无名信号量,所以说是没有名字的,不能使用sem_open打开

2.无名信号量 函数接口

1)定义一个无名信号量 (数据类型: sem_t)
sem_t sem; -> 无名信号量不是一个文件,而是一个变量
2)初始化无名信号量。 -> sem_init()

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
    sem: 无名信号量的变量的地址
    pshared:
        0  -> 作用于线程之间  -> 只考虑这种情况
        非0-> 作用于进程之间
    value: 无名信号量的起始值。
返回值:
    成功:0
    失败:-1

3)无名信号量的P操作

P操作: 资源数-1操作 -> sem_wait() -> man 3 sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem); -> 如果减不了1,这个函数就会阻塞
参数:
sem:无名信号量的地址
返回值:
成功:0 -> 资源数可以-1
失败:-1
比如:
如果当前的值为2,那么sem_wait()就会马上返回,并且将值设置为1。
如果当前的值为1,那么sem_wait()就会马上返回,并且将值设置为0。
如果当前的值为0,那么sem_wait()就会阻塞,一直阻塞到有名信号量的值不为0为止

4)无名信号量的V操作
V操作: 资源数+1操作 -> sem_post() -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> 一定可以+1的,绝对不会阻塞
参数:
sem:无名信号量的地址
返回值:
成功:0 -> 资源数可以+1
失败:-1

5)销毁无名信号量。 -> sem_destroy()

#include <semaphore.h>
int sem_destroy(sem_t *sem);
参数:
    sem:无名信号量的地址
返回值:
    成功:0
    失败:-1

例子:线程使用无名信号量实现互斥

#include<stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>

#include <semaphore.h>

//1、定义一个无名信号量变量
sem_t g_sem;

int g_val = 0;

//线程1的例程函数
void* start_routine1(void *arg)
{
    printf("%s start ...\n",__FUNCTION__);

    //在访问 共享资源之前,先看看有没有其他线程正在使用
    //无名信号量的P操作   
    //也就是说,能不能进行 -1 操作  如果能 则往下面 走
    //如果不能 ,则阻塞在这里
    sem_wait(&g_sem);

    g_val = 200; 
    sleep(2);
    printf("start_routine1 g_val:%d\n",g_val);

    //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    sem_post(&g_sem);

    printf("%s end ...\n",__FUNCTION__);
}

//线程2的例程函数
void* start_routine2(void *arg)
{
    printf("%s start ...\n",__FUNCTION__);

    sem_wait(&g_sem);

    sleep(1);
    g_val = 400;
    printf("start_routine2 g_val:%d\n",g_val);

    //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    sem_post(&g_sem);
    printf("%s end ...\n",__FUNCTION__);
}

//线程3 的 例程函数
void* start_routine3(void *arg)
{
    printf("%s start ...\n",__FUNCTION__);

    sem_wait(&g_sem);

    sleep(1);
    g_val = 600;
    printf("start_routine3 g_val:%d\n",g_val);

    //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    sem_post(&g_sem);
    printf("%s end ...\n",__FUNCTION__);
}

int main()
{
    //2、初始化无名信号量 ---sem_init
    sem_init(&g_sem,0, 1);


    //创建一条子线程
    pthread_t thread1;
    pthread_create(&thread1,NULL,start_routine1,NULL);

    pthread_t thread2;
    pthread_create(&thread2,NULL,start_routine2,NULL);

    pthread_t thread3;
    pthread_create(&thread3,NULL,start_routine3,NULL);


    sem_wait(&g_sem);
    g_val = 1000;
    printf("main g_val:%d\n",g_val);

    //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    sem_post(&g_sem);


    //阻塞等待子线程退出,回收资源
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    pthread_join(thread3,NULL);

    //销毁无名信号量
    sem_destroy(&g_sem);
}

练习6: 有一个进程,创建5个线程出来,每一个线程任务都是一样。
任务: 将"helloworld"字符串每隔1S打印一个字符。 -> 任务:10S
结果: hhhhheeeeellllllllllooooowwwwwooooorrrrrlllllddddd
1S 1S 1S 1S 1S 1S 1S 1S 1S 1S
代码参考:

#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <pthread.h>
#include <semaphore.h>

//线程1 的 例程函数
void* start_routine(void *arg)
{
    int cnt = 0;
    char str[] = "helloworld";
    while(1)
    {
    sleep(1);
    fprintf(stderr,"%c",str[cnt++]); //stde
    rr 标准出错 ,没有缓冲区
    if(cnt == strlen(str))
    break;
    }

    pthread_exit(NULL);
}

int main()
{
    //创建5条子线程

    pthread_t thread[5];

    for(int i=0; i<5; i++){
    pthread_create(&(thread[i]),NULL,start_routine,NULL);
    }

    //阻塞等待子线程退出,回收资源
    for(int i=0; i<5; i++){
        pthread_join(thread[i],NULL);
    }

    return 0;
}

练习7: 有一个进程,创建5个线程出来,每一个线程任务都是一样。 (使用无名信号量去处理同步互斥) -> 0是不能变成-1的
任务: 将"helloworld"字符串每隔1S打印一个字符。 -> 任务:10S
结果: helloworldhelloworldhelloworldhelloworldhelloworld
10S 10S 10S 10S 10S

代码参考:

#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <pthread.h>
#include <semaphore.h>

//1、定义一个无名信号量
sem_t g_sem;

void* start_routine(void*arg)
{
    //进行P 操作  -1 
    sem_wait(&g_sem);

    char str[ ] = "helloworld";
    for(int i=0;i<strlen(str);i++)
    {
        sleep(1);
        fprintf(stderr,"%c",str[i]);
    }
    //进行V操作 + 1
    sem_post(&g_sem);

    pthread_exit(0);
}

int main()
{
    //2、初始化无名信号量
    sem_init(&g_sem,0,1);

    //创建5条子线程
    pthread_t thread[5];

    for(int i=0; i<5; i++){
        pthread_create(&(thread[i]),NULL,start_routine,NULL);
    }

    //阻塞等待子线程退出,回收资源
    for(int i=0; i<5; i++){
        pthread_join(thread[i],NULL);
    }

    sem_destroy(&g_sem);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值