LINUX线程

一、守护进程

1.守护进程的特点

* 后台服务进程
* 独立于控制终端
* 周期性执行某任务
* 不受用户登录注销影响
* 一般采用以 d 结尾的名字(服务)

2.进程组

* 进程的组长
* 组里边的第一进程
* 进程组的 ID== 进程中的组长的 ID
* 进程中组长的选择
* 进程中的第一个进程
* 进程组ID 的设定
* 进程组的ID 就是组长的进程 ID

3.会话

* 创建一个会话注意事项
* 不能是进程组长(除了只有一个进程组的会话外)
* 创建会话的进程成为新进程组的组长
* 有些lInux 版本需要 root 权限执行此操作
* 创建出的新会话会丢弃原有的控制终端
* 一般步骤;fork , 父亲死,儿子执行创建会话操作( setid
* 获取进程所属的会话ID :pid_t getsid(pid_t pid);
* 创建一个会话 :pid_t setid(void);

4.创建守护进程模型

* fork子进程,父进程退出
* 子进程创建新会话
* 改变当前工作目录chdir
* 重设文件掩码
* 关闭文件描述符
* 执行核心工作

二、线程

1.主线程和子线程

共享:
* .text
* .bss
* .data
* 堆
* 动态加载区
* 环境变量
* 命令行参数
* -通信:全局变量,堆
不共享:
* 栈:一共五个线程,栈区被平均分成五块
在Linux下: 线程就是进程-轻量级进程
对于内核来说,线程就是进程
多进程和多线程的区别:
        多进程: 始终共享的资源 代码、文件描述符、内存映射区--mmap
        多线程:始终共享的资源:堆、全局变量,节省资源
安卓线程man page ,命令: sudo apt-get install manpages-posix-dev
查看指定线程的LWP号:
线程号和线程ID是有区别的 ,线程号是给内核看的
查看方式 :找到程序的进程ID->ps -Lf pid

2.线程的创建

int pthread_create( pthread_t *thread), // 线程 ID = 无符号长整型
                                const pthread_attr_t *attr, //线程属性,NULL
                                void *(*start_routine)(void *), //线程处理函数
                                void *arg); //线程处理函数
参数:
pthread: 传出参数,线程创建成功之后,会被设置一个合适的值
attr: 默认传 NULL
start_routine: 子线程的处理函数
arg: 回调函数的参数
返回值:
成功 :0
错误 : 错误号 //perror 不能使用该函数打印错误信息
主线程先退出,子线程会被强制结束
验证线程直接共享全局变量、
示例:
运行结果:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *myfun(void *arg)
{
	printf("子线程的ID=%ld\n",pthread_self());
	return 0;
}

int main()
{
	int i;
	pthread_t pthid;
	int rnt = pthread_create(&pthid,NULL,myfun,NULL);
	if(rnt != 0)
	{
		printf("线程创建失败\n");
		printf("错误信息是:%s\n",strerror(rnt));
	}
	printf("父线程的ID是=%ld\n",pthread_self());
	
	sleep(1);
	for(i=0;i<4;i++)
	{
	
		printf("i = %d\n",i);
	}

	return 0;
}

没有sleep时:
有sleep时:
PS:主线程和子线程也会抢夺CPU

3单个线程退出

exit(0):退出整个进程

函数原型: void pthread‐exit(void *retval)

retval 指针:必须指向全局,堆
功能:退出单个线程(没有释放资源)

4.阻塞等待线程退出,获取线程退出状态

功能:阻塞等待子线程,回收子线程资源

函数原型:
int pthread_join(pthread_t pthread, void **retval)
参数:
pthread: 要回收的子线程的 ID
retval: 读取线程退出的携带信息
传出参数
void* ptr;
pthread_join(pthid,&ptr)
指向的内存和 pthread_exit 参数指向地址一致
示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

int retvl = 100;
void *myfun(void *arg)
{
        printf("子线程的ID=%ld\n",pthread_self());

        pthread_exit(&retvl);//用退出单个线程函数来退出子线程

        printf("如果前面调用了退出单个线程函数,则该语句不执行\n");
        return 0;
}

int main()
{
        int i;
        void *ptr = NULL;
        pthread_t pthid;
        int rnt = pthread_create(&pthid,NULL,myfun,NULL);
        if(rnt != 0)
        {
                printf("线程创建失败\n");
                printf("错误信息是:%s\n",strerror(rnt));
        }
        printf("父线程的ID是=%ld\n",pthread_self());


        pthread_join(pthid,&ptr);
        printf("线程退出的携带信息%d\n",*(int *)ptr);
        for(i=0;i<4;i++)
        {

                printf("i = %d\n",i);
        }
        sleep(1);
        return 0;
}
运行结果:

5.线程分离--pthread_detach

函数原型: int pthread_datach(pthread_t thread);
* 调用该函数之后不需要 pthread_join
* 子线程会自动回收自己的 PCB

6.杀死(取消)线程--pthread_cancel

函数原型: int pthread_cancel(pthread_t pthread);
使用注意事项:
在要杀死的子线程对应的处理的函数的内部,必须做过一次系统调用 write read printf
int a = 2; int b = a+3;
pthread_testcancel();设置取消点

7.比较两个线程ID是否相等(预留函数) --pthread_equal

函数原型:
int pthread_equal(pthread_t t1,pthread_t t2);

8.通过属性设置线程的分离

1. 线程属性类型: pthread_attr_t attr;
2. 线程属性操作函数:
对线程属性变量的初始化 :int pthread_attr_init(pthread_attr_t* attr);
设置线程分离属性:int pthread_attr_setdetachstate( pthread_attr_t* attr,int detachstate );
参数:
attr : 线程属性
detachstate:
        PTHREAD_CREATE_DETACHED(分离 )
        PTHREAD_CREATE_JOINABLE(非分离 )
释放线程资源函数 :int pthread_attr_destroy(pthread_attr_t* attr);
示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

int retvl = 100;
void *myfun(void *arg)
{
        printf("子线程的ID=%ld\n",pthread_self());

        return 0;
}

int main()
{
        int i;
        pthread_t pthid;
        pthread_attr_t attr;

        //初始化分离线程
        pthread_attr_init(&attr);
        //设置分离线程
        pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
        int rnt = pthread_create(&pthid,&attr,myfun,NULL);
        if(rnt != 0)
        {
                printf("线程创建失败\n");
                printf("错误信息是:%s\n",strerror(rnt));
        }
        printf("父线程的ID是=%ld\n",pthread_self());

        sleep(1);
        for(i=0;i<4;i++)
        {

                printf("i = %d\n",i);
        }
        //释放资源
        pthread_attr_destroy(&attr);
        return 0;
}
运行结果:

9.线程同步

数据混乱的原因:
        *操作了共享资源
        *CPU调度问题

示例:
#include <stdio.h>

#include <pthread.h>
#include <unistd.h>
int number;

void *fun1(void *arg)
{
        int i;
        for(i=0;i<10;i++)
        {
                int rnt = number;
                rnt++;
                number = rnt;
                printf("fun1 id = %ld,number =%d\n",pthread_self(),number);
        }

}
void *fun2(void *arg)
{
        int i;
        for(i=0;i<10;i++)
        {
                int rnt = number;
                rnt++;
                number = rnt;
                printf("fun2 id = %ld,number =%d\n",pthread_self(),number);
        }
}


int main()
{
        pthread_t pid1,pid2;

        pthread_create(&pid1,NULL,fun1,NULL);
        pthread_create(&pid2,NULL,fun2,NULL);



        pthread_join(pid1,NULL);
        pthread_join(pid2,NULL);
        return 0;
}
运行结果:

10.互斥锁(互斥量)

1. 互斥锁类型
创建一把锁: pthread_mutex_t mutex;
2. 互斥锁的特点:
多个线程访问共享数据的时候是串行的,(可以解决线程同步数据混乱的问题)
3. 使用互斥锁缺点
效率低
4. 互斥锁使用的步骤
创建互斥锁: pthread_mutex_t mutex;
初始化: pthread_mutex_init(&mutex,NULL); -- mutex = 1
找到线程共同操作的共享数据
加锁:操作共享资源之前加锁, pthread_mutex_lock(&mutex); // 阻塞 --mutex = 0
pthread_mutex_trylock(&mutex); // 如果锁上锁直接返回,不阻塞
XXXXXX 共享数据操作 // 临界区 ,越小越好(目的是为了共享数据,如果临界区域里面有过多的非共享数据,会出现bug)
解锁: pthread_mutex_unlock(&mutex); // -- mutex = 1
阻塞在锁上的线程会被唤醒
销毁: pthread_mutex_destory(&mutex)

互斥锁相关函数:

初始化互斥锁:
pthread_mutex_init( pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr, );
销毁互斥锁:
pthread_mutex_destory(pthread_mutex_t* mutex );
加锁:
pthread_mutex_lock(pthread_mutex* mutex)
mutex:
        没有被锁上:当前线程会将这把锁锁上
        被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞 尝试加锁,失败返回,不阻塞
尝试加锁:
pthread_mutex_trylock(pthread_mutex_t* mutex);
没有锁上:当前线程会被这把锁加锁
如果锁上了:不会阻塞,返回
返回 0 :加锁 成功。没锁上:返回错误号
if( pthread_mutex_trylock(& mutex)==0)
{
// 尝试加锁,并且成功了
// 访问共享资源
XXXXXXXX
}
else
{
// 错误处理
// 或者等待,再次尝试加锁
}
解锁
pthread_mutex_unlock(pthread_mutex_t* mutex)
如果我们想要使用互斥锁同步线程所以线程都需要加锁
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number;
pthread_mutex_t mutex;
void *fun1(void *arg)
{
        int i;
        for(i=0;i<10;i++)
        {
                pthread_mutex_lock(&mutex);
                int rnt = number;
                rnt++;
                number = rnt;
                printf("fun1 id = %ld,number =%d\n",pthread_self(),number);
                pthread_mutex_unlock(&mutex);
        }
}
void *fun2(void *arg)
{
        int i;
        for(i=0;i<10;i++)
        {
                pthread_mutex_lock(&mutex);
                int rnt = number;
                rnt++;
                number = rnt;
                printf("fun2 id = %ld,number =%d\n",pthread_self(),number);
                pthread_mutex_unlock(&mutex);
        }
}
int main()
{
        pthread_t pid1,pid2;
        //初始化锁
        pthread_mutex_init(&mutex,NULL);
        pthread_create(&pid1,NULL,fun1,NULL);
        pthread_create(&pid2,NULL,fun2,NULL);
        pthread_join(pid1,NULL);
        pthread_join(pid2,NULL);
        //销毁锁
        pthread_mutex_destroy(&mutex);
        return 0;
}

运行结果:

11.死锁

造成死锁的原因:自己锁自己

例如:

for(int i = 0;i<MAX;i++)

{
        pthread_mutex_lock(&mutex);//锁在这里已经被使用
        pthread_mutex_lock(&mutex);//会阻塞在这里,锁已经被前面使用了,等待前面释放锁
        int crt = number;
        crt++;
        number = crt;
        printf("thread A id = %ld,number = %d\n",pthread_self(),number);
        pthread_mutex_unlock(&mutex);
        usleep(10);
}
PS:操作完锁之后,一定要先解锁,之后再进行操作
线程 1 对共享资源 A 加锁成功 -A
线程 2 对共享资源 B 加锁成功 -B
线程 1需要 访问共享资源 B时 ,线程1对 B 锁加锁 -由于线程2还没有释放B锁, 线程 1 阻塞在 B 锁上(等待B锁)
线程2 需要 访问共享资源A ,线程2对A 锁加锁 -由于线程1还没有释放B锁, 线程2 阻塞在A 锁上(等待A锁)
如何解决:
- 让线程按照一定的顺序去访问共享资源 - 在访问其他锁的时候,需要先将自己的锁解开
--try_lock

12.读写锁

1. 读写锁是几把锁?
一把锁
pthread_rwlock_t lock;
2. 读写锁的类型;
读锁 - 对内存做读操作
写锁 - 对内存做写操作
4. 读写锁的特性:
* 线程 A 加读锁成功,又来了三个线程,做读操作,可以加锁成功
  读共享- 并行处理
* 线程 A 加写锁成功,又来了三个线程,做读操作,三个线程阻塞
  写独占
* 线程 A 加读锁成功,又来了 B 线程加写锁阻塞,又来了 C 线程加读锁阻塞
  读写不可以同时进行,写的优先级高
5. 读写锁场景练习
* 线程 A 加写锁成功,线程 B 请求读锁
  线程 B 阻塞
* 线程 A 持有读锁,线程 B 请求写锁
   线程 B 阻塞
* 线程 A 拥有读锁,线程 B 请求读锁
  线程 B 加锁
* 线程 A 持有读锁,然后线程 B 请求写锁,然后线程 C 请求读锁
  线程 B 阻塞,线程 C 阻塞
  线程 B 加锁,线程 C 阻塞
  线程 C 加锁
* 线程 A 持有写锁,然后线程 B 请求读锁,然后线程 C 请求写锁
  线程 B 阻塞,线程 C 阻塞
  线程 C 加锁, 线程 B 阻塞
  线程 B 加锁
6. 读写锁的适用场景
互斥锁 - 读写串行
读写锁:
读:并行
写:串行
程序中的读操作 > 写操作的时候
7. 主要操作函数
初始化读写锁
pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr );
销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t* rwlock):
加读锁
pthread_rwlock_rdlock(pthread_rwlock_t* rdlock);
阻塞:之前对这把锁加的是写锁的操作
尝试加读锁
pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
加锁成功:返回 0
失败:返回错误号
加写锁
pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
阻塞:上一次加写锁还没解锁
阻塞:上一次加读锁还没解锁
尝试加写锁
pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
解锁
pthread_rwlock_unlock(pthread_rwlock_t* rwlock)
8. 练习
三个线程不定时写同一个全局变量,五个线程不定时期读同一全局资源
先不加锁
示例:不加读写锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number = 0;

void *writeFun(void *arg)
{
        while(1)
        {
                number++;
                printf("id = %ld,write number=%d\n",pthread_self(),number);
                usleep(1);
        }
}

void *readFun(void *arg)
{
        while(1)
        {
                printf("id = %ld,read number = %d\n",pthread_self(),number);
                usleep(1);
        }
}


int main()
{
        int i;
        pthread_t p[8];
        for(i = 0;i < 3;i++)
        {
                pthread_create(&p[i],NULL,writeFun,NULL);//多个子线程操作同一个函数,所以只需要用同一个回调函数就好
        }
        for(i = 3;i<8;i++)
        {
                pthread_create(&p[i],NULL,readFun,NULL);
        }
        for(i = 0;i<8;i++)
        {
                pthread_join(p[i],NULL);
        }
}

PS:运行结果会出现,资源混乱的情况,例如上面已经写进去了3948了,但是还是读到3946,加读写锁就不会出现线程同步时,资源混乱的问题

示例:有加读写锁

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number = 0;
pthread_rwlock_t rwlock;
void *writeFun(void *arg)
{
        while(1)
        {
                pthread_rwlock_wrlock(&rwlock);//加写锁
                number++;
                printf("id = %ld,write number=%d\n",pthread_self(),number);
                pthread_rwlock_unlock(&rwlock);//解锁
                usleep(500);
        }
}

void *readFun(void *arg)
{
        while(1)
        {
                pthread_rwlock_rdlock(&rwlock);//加读锁
                printf("id = %ld,read number = %d\n",pthread_self(),number);
                pthread_rwlock_unlock(&rwlock);
                usleep(500);
        }
}
int main()
{
        int i;
        pthread_t p[8];
        pthread_rwlock_init(&rwlock, NULL);//初始化读写锁
        for(i = 0;i < 3;i++)
        {
                pthread_create(&p[i],NULL,writeFun,NULL);//多个子线程操作同一个函数,所以只需要用同一个回调函数就好
        }
        for(i = 3;i<8;i++)
        {
                pthread_create(&p[i],NULL,readFun,NULL);
        }
        for(i = 0;i<8;i++)
        {
                pthread_join(p[i],NULL);
        }
        pthread_rwlock_destroy(&rwlock);//销毁读写锁
}

13.条件变量

1. 条件变量是锁吗?
不是锁 ,但是条件变量能够阻塞线程
使用条件变量 + 互斥量
互斥量:保护一块共享数据
条件变量:引起阻塞 ,如:生产者和消费者模型
2. 条件变量的两个动作
条件不满足,阻塞线程
当条件满足,通知阻塞的线程开始工作
3. 条件变量的类型
pthread_cond_t cond ;
conditon 条件
4. 主要函数:
初始化一个条件变量:
pthread_cond_init(pthread_cond_t * restrict cond, const pthread_condattr_t * restrict attr );
销毁一个条件变量:
pthread_cond_destroy(pthread_cond_t * cond);
阻塞等待一个条件变量:
pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t * restrict mutex );
       *  阻塞线程
       *  将已经上锁的mutex 解锁
       *  该函数解除阻塞,对互斥锁加锁
限时等待一个条件变量:
pthread_cond_timedwait( pthread_cond_t * restrict cond, pthread_mutex_t * restrict mutex,
const struct timespec * restrict abstime );
唤醒至少一个阻塞在条件变量上的线程:
pthread_cond_signal(pthread_cond_t* cond);
唤醒全部阻塞在条件变量上的线程:
pthread_cond_broadcast(pthread_cond_t * cond)
练习 :使用条件变量实现生产者,消费者模型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值