线程
线程的概念:轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程。
线程是最小的执行单位,进程是最小的系统资源分配单位。
内核实现都是通过clone函数实现的。
线程也有自己的PCB。
线程非共享资源:1. 线程id;2. 处理器现场和栈指针(内核栈);3. 独立的栈空间(用户空间栈);3. 信号屏蔽字;4. 调度优先级。
线程共享资源:1. text;2. data;3. rodata, 4. bss;4. heap;5. errno变量。
线程的优点:1. 提高并发性; 2. 占用资源小;3. 通信方便。
线程的缺点:1. 调试困难;2. 库函数,不稳定;3. 对信号支持不好。
缺点可以克服,优点很突出。
线程的操作函数
pthread_t pthread_self(void);
获取线程id。 线程id是在进程地址空间内部,用来标识线程身份的id号。
返回值:本线程id。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
创建线程。
参1:传出参数,表新创建的子线程 id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
参4:参3的参数。没有的话,传NULL
返回值:成功为0;失败:errno。
void pthread_exit(void *retval);
retval:退出值。 无退出值时,NULL
exit(); 退出当前进程。
return: 返回到调用者那里去。
pthread_exit(): 退出当前线程。
int pthread_join(pthread_t thread, void**retval);
阻塞 回收线程。
thread: 待回收的线程id。
retval:传出参数。 回收的那个线程的退出值。线程异常退出值为 -1。
返回值:成功:0;失败:errno。
int pthread_cancel(pthread_t thread);
杀死一个线程。 需要到达取消点(保存点)。如果,子线程没有到达取消点, 那么 pthread_cancel 无效。我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();成功被 pthread_cancel() 杀死的线程,使用pthead_join 回收。
thread: 待杀死的线程id,
返回值:成功:0;失败:errno。
int pthread_detach(pthread_t thread);
设置线程分离,分离后的线程会自动回收,不能用pthread_join再次回收。
thread:待分离分线程id。
返回值:成功为0;失败为errno。
int pthread_equal(pthread_t t1, pthread_t t2);
比较两个线程是否相同,如果一样返回一个非零值,不一样返回0。
线程属性控制
pthread_attr_t attr; 创建一个线程属性结构体变量
int pthread_attr_init(pthread_attr_t*attr); --初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
--设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL);
--借助修改后的设置线程属性,创建为分离态的新线程
int pthread_attr_destroy(pthread_attr_t*attr); --销毁线程属性
线程和进程相关函数
线程控制原语 进程控制原语
pthread_create() fork();
pthread_self() getpid();
pthread_exit() exit(); / return
pthread_join() wait()/waitpid()
pthread_cancel() kill()
pthread_detach()
//01_pthread_create.c
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void* thr(void *arg)
{
printf("I am thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am a main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
sleep(1);
return 0;
}
//02_pthread_exit.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void* thr(void *arg)
{
printf("I am a thread, tid = %lu\n", pthread_self());
//return NULL;
pthread_exit(NULL);
//exit(1);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am main thread, tid = %lu\n", pthread_self());
sleep(5);
printf("I will out\n");
pthread_exit(NULL);
return 0;
}
//03_pthread_return.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
void* thr(void *arg)
{
printf("I am a thread, tid = %lu\n", pthread_self());
sleep(5);
printf("I am a thread, tid = %lu\n", pthread_self());
pthread_exit((void*)100);
//return (void*)100;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
void *ret;
pthread_join(tid, &ret); //线程回收
printf("ret exit with %d\n", (int)ret);
return 0;
}
//04_pthread_cancel.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#
void* thr(void *arg)
{
while (1)
{
printf("I am a thread, tid = %lu\n", pthread_self());
sleep(1);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
sleep(3);
if (pthread_cancel(tid) != 0)
{
perror("kill thread error!");
return -1;
}
void *ret;
pthread_join(tid, &ret);
printf("thread exit with %d\n", (int)ret);
return 0;
}
//05_pthread_detach.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void* thr(void *arg)
{
printf("I am a thread, self tid = %lu\n", pthread_self());
sleep(3);
printf("I am a thread, self tid = %lu\n", pthread_self());
return NULL;
}
int main(int argc, char const *argv[])
{
int ret = -1;
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
ret = pthread_detach(tid); //线程分离
if (ret != 0)
{
perror("pthread_detach error!");
}
//sleep(5);
ret = pthread_join(tid, NULL);
if (ret != 0)
{
printf("pthread_join error, err:%d\n", ret);
}
return 0;
}
//06_pthread_var.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
int var = 100;
void* thr(void *arg)
{
sleep(1);
printf("I am a thread, self tid = %lu, var = %d\n", pthread_self(), var);
sleep(2);
var = 1001;
printf("I am a thread, self tid = %lu, var = %d\n", pthread_self(), var);
return NULL;
}
int main(int argc, char const *argv[])
{
int ret = -1;
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
ret = pthread_detach(tid); //线程分离
if (ret != 0)
{
perror("pthread_detach error!");
}
printf("I am a main thread, self tid = %lu, var = %d\n", pthread_self(), var);
var = 1003;
sleep(5);
printf("I am a main thread, self tid = %lu, var = %d\n", pthread_self(), var);
ret = pthread_join(tid, NULL);
if (ret != 0)
{
printf("pthread_join error, err:%d\n", ret);
}
return 0;
}
//07_npthread.c
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void* thr(void *arg)
{
int num = (int)arg;
printf("I am %d thread, self = %lu\n", num, pthread_self());
return (void *)(100 + num);
}
int main(int argc, char const *argv[])
{
pthread_t tid[5];
int i = 0;
for (; i < 5; ++i)
{
pthread_create(&tid[i], NULL, thr, (void*)i);
}
for (i = 0; i < 5; ++i)
{
void *ret;
pthread_join(tid[i], &ret);
printf("i = %d, ret = %d\n", i, (int)ret);
}
return 0;
}
//08_pthread_attr.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* thr(void *arg)
{
printf("I am a thread\n");
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_attr_t attr;
pthread_attr_init(&attr); //初始化属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置属性隔离
pthread_t tid;
pthread_create(&tid, &attr, thr, NULL);
sleep(3);
int ret = pthread_join(tid, NULL);
if (ret > 0)
{
printf("join err:%d, %s\n", ret, strerror(ret));
}
pthread_attr_destroy(&attr); //销毁属性
return 0;
}
//09_pthread_print.c
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
int sum = 0;
void* thr1(void *arg)
{
while (1)
{
printf("hello ");
sleep(rand() % 3);
printf("world\n");
sleep(rand() % 3);
}
}
void* thr2(void *arg)
{
while (1)
{
printf("HELLO ");
sleep(rand() % 3);
printf("WORLD\n");
sleep(rand() % 3);
}
}
int main(int argc, char const *argv[])
{
pthread_t tid[2];
pthread_create(&tid[0], NULL, thr1, NULL);
pthread_create(&tid[1], NULL, thr2, NULL);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}
互斥锁
线程访问一个共享资源,需要协调步骤!
线程同步:协调步调,对公共数据按顺序执行。防止数据混乱,产生与时间有关的错误。
数据混乱的原因:1.资源共享(独享资源则不会);2.调度随机(意味着数据访问会出现竞争);3.线程间缺乏必要的同步机制。
前两点不能改变,欲提高效率,传递数据,资源必须共享,只要共享资源,就一定会出现竞争。只能解决第3个原因带来的数据混乱问题。
解决同步的问题:加锁!使多个线程在访问共享资源的时候,出现互斥。
互斥量mutex
每个线程在岁资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争大。
使用mutex的一般步骤:
1.pthread_mutex_t lock; //创建锁
//pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。变量mutex只有两种取值:0,1
2.pthread_mutex_init //初始化 1
3.pthread_mutex_lock //加锁 1-- -> 0
4.访问共享数据
5.pthread_mutex_unlock ///解锁 0++ -> 1
6.pthread_mutex_destroy //销毁锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
restrict关键字:用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。
pthread_mutex_t mutex;
1. pthread_mutex_init(&mutex, NULL); ///动态初始化。
2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态初始化。
注意事项:
1.尽量保证锁的粒度,越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)
2.互斥锁,本质是结构体。我们可以看成整数。初值为1。(pthread_mutex_init函数调用成功)
3.加锁。--操作,阻塞线程。
4.解锁。++操作,唤醒阻塞在锁上的线程。
5.try锁,尝试加锁,成功加锁成功(--);失败直接返回错误号(如EBUSY),不阻塞。
//mutex
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
pthread_mutex_t mutex; //定义一把互斥锁
void sys_err(const char* str)
{
perror(str);
exit(1);
}
void* thr(void* arg)
{
srand(time(nullptr));
while (true)
{
if (pthread_mutex_lock(&mutex)) //加锁,可以想象成锁--(1-- -> 0)
{
sys_err("pthread mutex lock error!\n");
}
printf("hello ");
sleep(rand() % 3); //模拟长时间操作共享资源,导致CPU易主,产生与实践有关的错误
printf("world!\n");
if (pthread_mutex_unlock(&mutex)) //解锁,可以想象成锁++(0++ -> 1)
{
sys_err("pthread mutex unlock error!\n");
}
sleep(rand() % 3);
}
pthread_exit(nullptr);
}
int main(int argc, char const *argv[])
{
pthread_t tid;
srand(time(nullptr));
if (pthread_mutex_init(&mutex, nullptr)) //初始化互斥锁, 可以认为,锁的值是1
{
sys_err("pthread mutex init failed!\n");
}
if (pthread_create(&tid, nullptr, thr, nullptr))
{
sys_err("pthread create error!\n");
}
//主线程
while (true)
{
if (pthread_mutex_lock(&mutex)) //加锁,可以想象成锁--(1-- -> 0)
{
sys_err("pthread mutex lock error!\n");
}
printf("HELLO ");
sleep(rand() % 3); //模拟长时间操作共享资源,导致CPU易主,产生与实践有关的错误
printf("WORLD!\n");
if (pthread_mutex_unlock(&mutex)) //解锁,可以想象成锁++(0++ -> 1)
{
sys_err("pthread mutex unlock error!\n");
}
sleep(rand() % 3);
}
if (pthread_join(tid, nullptr))
{
sys_err("pthread join error!");
}
if (pthread_mutex_destroy(&mutex))
{
sys_err("pthread mutex destroy error!\n");
}
return 0;
}
读写锁
读写锁:锁只有一把,以读方式给数据加锁–读锁;以写方式给数据加锁–写锁。读共享、写独占。写锁优先级高。
读写锁也叫共享-独占锁。当读写锁以“读模式加锁”时,它是以共享模式锁住的;当它以“写模式加锁”时,它是以独占模式锁住的。写独占、读共享。
读写锁非常适合于对数据结构读的次数远大于写的情况。
1.读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会阻塞;
2.读写锁是“读模式加锁”时,如果线程以“读模式加锁”会成功;如果线程以“写模式加锁”会阻塞。
3.读写锁是“读模式加锁”时,既有试图以“写模式加锁”的线程,也有试图以“读模式加锁”的线程,那么读写锁会阻塞随后的“读模式加锁”请求,优先满足“写模式加锁”请求。读锁、写锁并行阻塞,写锁优先级高。
pthread_rwlock_t 类型 用于定义一个读写锁变量
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,失败返回错误号。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
int counter = 0; //共享资源
pthread_rwlock_t rwlock;
void sys_err(const char *str)
{
perror(str);
exit(1);
}
//写线程的回调函数
void *th_write(void *arg)
{
int t;
long i = reinterpret_cast<long>(arg);
while (true)
{
//写线程期间,独占
if (pthread_rwlock_wrlock(&rwlock))
{
sys_err("pthread rwlock wrlock error!\n");
}
t = counter; //保存写之前的值
sleep(1); //模拟业务,让读线程获取CPU,但是读线程因为锁被锁住而堵塞
printf("=======write %ld: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
if (pthread_rwlock_unlock(&rwlock))
{
sys_err("pthread relock unlock error!\n");
}
sleep(5); //让其它线程获取锁,防止本线程回到while又重新上锁,导致其它线程很少有机会获取到锁
}
pthread_exit(nullptr);
}
//读线程回调函数
void *th_read(void *arg)
{
long i = reinterpret_cast<long>(arg);
while (true)
{
//读线程期间,读时共享
if (pthread_rwlock_wrlock(&rwlock))
{
sys_err("pthread rwlock wrlock error!\n");
}
printf("=======read %d: %lu: counter=%d\n", i, pthread_self(), counter);
if (pthread_rwlock_unlock(&rwlock))
{
sys_err("pthread relock unlock error!\n");
}
sleep(1); //让其它线程获取锁,防止本线程回到while又重新上锁,导致其它线程很少有机会获取到锁
}
pthread_exit(nullptr);
}
int main(int argc, char const *argv[])
{
pthread_t tid[8];
if (pthread_rwlock_init(&rwlock, nullptr))
{
sys_err("pthread rwlock init error!\n");
}
//创建三个写线程
for (int i = 0; i < 3; ++i)
{
if (pthread_create(&tid[i], nullptr, th_write, reinterpret_cast<void *>(i)))
{
sys_err("pthread create error!\n");
}
}
//创建五个读线程
for (int i = 2; i < 8; ++i)
{
if (pthread_create(&tid[i], nullptr, th_read, reinterpret_cast<void *>(i)))
{
sys_err("pthread create error!\n");
}
}
//回收子线程资源
for (int i = 0; i < 8; ++i)
{
if (pthread_join(tid[i], nullptr))
{
sys_err("pthread join error!\n");
}
}
//释放读写锁
if (pthread_rwlock_destroy(&rwlock))
{
sys_err("pthread rwlock unlock error!\n");
}
return 0;
}
死锁现象
使用锁不恰当造成的现象。
- 对一个锁反复lock。
- 两个线程,各自持有一把锁,请求另一把。
条件变量
本身不是锁,但是通常结合锁来使用。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //动态初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); //阻塞等待条件
作用:
1. 阻塞等待条件变量满足;
2. 解锁已经加锁成功的信号量(相当于pthread_mutex_unlock(&mutex))。
1、2两步是一个原子操作。
3. 当条件满足,函数返回时,解除阻塞并重新申请获取互斥锁。重新加锁信号量(相当于pthread_mutex_lock(&mutex))。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒阻塞在条件变量上的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒阻塞在条件边上的(至少)一个线程
#include <iostream>
#include <string>
#include <memory>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //定义互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; //定义一个条件变量
void sys_err(string str)
{
cout << str << endl;
pthread_exit(nullptr);
}
class msg
{
public:
int num;
shared_ptr<msg> next;
};
shared_ptr<msg> head = nullptr; //头结点
void *producer(void *arg)
{
while (true)
{
//生产一个数据
shared_ptr<msg> mp(new msg()); //生产的数据节点
mp->num = rand() % 1000 + 1;
cout << "--produce:" << mp->num << endl;
//加锁 互斥量
if (pthread_mutex_lock(&lock))
sys_err("pthread mutex lock error!");
//写共享区域
mp->next = head;
head = mp;
//解锁 互斥量
if (pthread_mutex_unlock(&lock))
sys_err("pthread mutex unlock error!");
//唤醒阻塞在条件变量has_data上的线程
if (pthread_cond_signal(&has_data))
sys_err("pthread cond signal error!");
//让消费者拿到锁
sleep(rand() % 3);
}
pthread_exit(nullptr);
}
void *consumer(void *arg)
{
while (true)
{
shared_ptr<msg> mp_c(new msg());
//加锁 互斥量
if (pthread_mutex_lock(&lock))
sys_err("pthread mutex lock error!");
//阻塞等待条件变量,解锁
if (head == nullptr)
{
if (pthread_cond_wait(&has_data, &lock))
sys_err("pthread cond wait error!");
}
mp_c = head;
head = mp_c->next;
//解锁 互斥量
if (pthread_mutex_unlock(&lock))
sys_err("pthread mutex unlock error!");
cout << "----consumer:" << mp_c->num << endl;
sleep(rand() % 3);
}
}
int main(int argc, char const *argv[])
{
srand(time(nullptr));
pthread_t tid1, tid2;
if (pthread_create(&tid1, nullptr, producer, nullptr))
sys_err("pthread create producer error!");
if (pthread_create(&tid1, nullptr, consumer, nullptr))
sys_err("pthread create producer error!");
if (pthread_join(tid1, nullptr))
sys_err("pthread join producer error!");
if (pthread_join(tid2, nullptr))
sys_err("pthread join consumer error!");
return 0;
}