- 线程标识符:typedef unsigned long int pthread_t;
创建线程
-
pthread_create 函数:
头文件:#include <pthread.h> 原函数:pthread_create(pthread *restrict tid const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg) 参数一:线程标识符,新线程ID,创建一个进程。如果成功则新线程的ID回填充到tidp的内存。 参数二:设置线程属性(调度策略,继承性,分高性),默认值NULL 参数三:回调函数(新线程要执行的函数),一旦线程被创建就会执行 参数四:回调函数的参数,必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。 返回值:成功返回0,失败则返回错误码。 注:创建线程成功后,新创建的线程则运行参数三和参数四,原来的线程则继续运行下一行代码。
结束线程
-
pthread_exit 函数:
原函数:extern void pthread_exit P ((void *__retval)) __attribute ((noreturn)); 只有一个参数:函数的返回代码。 显式的退出一个线程。通常情况,在线程完成后,不再调用时,调用此函数。 如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
线程等待
-
pthread_join 函数:
原函数:int pthread_join(pthread_t tid,void ** rval); 参数一:指定线程的 id,被等待的线程标识符 参数二:一个用户定义的指针,指定线程的返回码,如果线程被取消,那么 rval被置为 PTHREAD_CANCELED。它可以用来存储被等待线程的返回值。 返回值:成功会返回0,失败返回错误码 这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。 注:*调用此函数会使指定的线程处于分离的状态,如果指定线程已经处于分离状态,那么调用就会失败。 *一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用此函数的线程则返回错误代码 ESRCH。
- 例1:
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5 //线程个数
void *say_hello(void *args)
{
printf("Hello!\n");
}
int main()
{
//定义线程的 id 变量,多个变量使用数组
pthread_t tids[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
if (ret != 0) {
printf("pthread_create error: error_code = %d\n", ret);
}
}
//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
pthread_exit(NULL);
}
执行结果:
Hello!
Hello!
Hello!
Hello!
Hello!
- 例2:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
void print_id(char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid is %u, tid is 0x%x\n",s,pid,(unsigned int)tid);
}
void *thread_fun(void *arg)
{
print_id(arg);
return (void *)0;
}
int main()
{
pthread_t ntid;
int err;
err = pthread_create(&ntid,NULL,thread_fun,"new thread: ");
if(err!=0)
{
printf("create new thread fail\n");
return 0;
}
print_id("main thread:");
sleep(2);
return 0;
}
执行结果:
main thread: pid is 4857, tid is 0xfd6b9700
new thread: pid is 4857, tid is 0xfcecb700
- 例3:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程一
void thread_1(void) {
int i = 0;
for(i = 0; i <= 6; i++) {
printf("This is a pthread_1.\n");
if(i == 2) {
pthread_exit(0);
}
sleep(1);
}
}
// 线程二
void thread_2(void) {
int i;
for(i = 0; i < 3; i++) {
printf("This is a pthread_2.\n");
}
pthread_exit(0);
}
int main(void) {
pthread_t id_1, id_2;
int ret;
/*创建线程一*/
ret=pthread_create(&id_1, NULL, (void *) thread_1, NULL);
if(ret != 0) {
printf("Create pthread error!\n");
return -1;
}
/*创建线程二*/
ret=pthread_create(&id_2, NULL, (void *) thread_2, NULL);
if(ret != 0) {
printf("Create pthread error!\n");
return -1;
}
/*等待线程结束*/
pthread_join(id_1, NULL);
pthread_join(id_2, NULL);
return 0;
}
执行结果:
This is a pthread1
This is a pthead2
This is a pthead2
This is a pthead2
This is a pthread1
This is a pthread1
互斥量(锁)
互斥量本质就是锁,访问共享资源前,对互斥量加锁,访问完成后解锁。
另一种解释:在访问临界资源时,通过互斥,限制同一时刻最多只能有一个线程可以获取临界资源。
、
目的: 为了让线程访问数据不产生冲突,使得同一时刻只有一个线程可以访问变量,其他线程无法进行访问并阻塞,数据得到同步。
、
逻辑: 如果访问临街资源发现没有其他线程上锁,就上锁,获取临界资源,期间如果其他线程执行到互斥锁发现已锁住,则线程挂起等待解锁,当前线程访问完临界资源后,解锁并唤醒其他被该互斥锁挂起的线程,等待再次被调度执行。
、
缺点: 要么加锁要么不加锁,而且同一时刻只允许一个线程对其加锁。
、
线程同步: 当多个线程共享相同的内存时,其中一个线程修改变量,其他线程也可以读取或者修改这个修改后的变量。
、
互斥量用 pthread_mutex_t类型的数据表示
简单流程:初始化锁 —> 上锁 —> 解锁 —> 销毁锁
-
静态初始化互斥量
pthread_mutex_t theMutex; pthread_mutex_t result = PTHREAD_MUTEX_INITIALIZER;
-
动态初始化互斥量
原函数:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr) 参数一:要初始化的互斥量 参数二:互斥量的属性(可以写NULL) 返回值:成功返回0,失败返回错误码
-
加锁
原函数:int pthread_mutex_lock(pthread_mutex_t *mutex) 只有一个参数:要加锁的互斥量 返回值:成功返回0,失败返回错误码 注:如果互斥量已经被锁住,那么会导致该线程阻塞。
-
尝试加锁
原函数:int pthread_mutex_trylock(pthread_mutex_t *mutex) 只有一个参数:要加锁的互斥量 返回值:成功返回0,失败返回错误码 注:如果互斥量已经被锁住,不会导致线程阻塞。 与 pthread_mutex_lock不一样的是:当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。
-
解锁
原函数:int pthread_mutex_unlock(pthread_mutex_t *mutex) 只有一个参数:要解锁的互斥量 返回值:成功返回0,失败返回错误码 注:如果一个互斥量没有被锁住(加锁函数和解锁函数要成对出现),否则解锁就会出错。
-
销毁锁
原函数:int pthread_mutex_destory(pthread_mutex_t *mutex) 只有一个参数:要销毁的互斥量 作用:动态分配的互斥量释放内存
举个栗子:
- 不上锁,线程不同步情况
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#define LEN 100000
int num = 0;
void* thread_func(void* arg) {
for (int i = 0; i< LEN; ++i) {
num += 1;
}
return NULL;
}
int main() {
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, (void*)thread_func, NULL);
pthread_create(&tid2, NULL, (void*)thread_func, NULL);
char* rev = NULL;
pthread_join(tid1, (void *)&rev);
pthread_join(tid2, (void *)&rev);
printf("correct result=%d, wrong result=%d.\n", 2*LEN, num);
return 0;
}
执行结果:
correct result=200000, wrong result=106860
- 上锁,线程同步
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#define LEN 100000
int num = 0;
void* thread_func(void* arg) {
pthread_mutex_t* p_mutex = (pthread_mutex_t*)arg;
for (int i = 0; i< LEN; ++i) {
pthread_mutex_lock(p_mutex);
num += 1;
pthread_mutex_unlock(p_mutex);
}
return NULL;
}
int main() {
pthread_mutex_t m_mutex;
pthread_mutex_init(&m_mutex, NULL);
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, (void*)thread_func, (void*)&m_mutex);
pthread_create(&tid2, NULL, (void*)thread_func, (void*)&m_mutex);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&m_mutex);
printf("correct result=%d, result=%d.\n", 2*LEN, num);
return 0;
}
执行结果:
correct result=200000, result=200000
一些补充:
一、锁保护的并不是我们的共享变量(或者说是共享内存),对于共享的内存而言,用户是无法直接对其保护的,因为那是物理内存,无法阻止其他程序的代码访问。事实上,锁之所以对关键区域进行了保护,是因为所有线程都遵循了一个规则,那就是在进入关键区域钱加同一把锁,在退出关键区域前释放同一把锁。
、
二、加锁是会带来额外的开销的,加锁的代码其运行速度,明显比不加锁的要慢一些,所以,在使用锁的时候,要合理,在不需要对关键区域进行保护的场景下,我们便不要画蛇添足,为其加锁了
读写互斥量(读写锁)
概述: 与互斥量类似,相关函数都差不多,另外一种线程同步机制,只不过比互斥量具有更高的并行性。
、
运用场景: 适用于对数据结构,读的次数远远大于写的次数
、
与互斥量不同点:
- 互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否允许线程进入。
- 相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁。
、
规则:
- 一次只有一个线程可以占有写模式读写锁,但是可以有多个线程同时占有读模式的读写锁。
- 当读写锁处于写加锁状态时,所有试图对这个锁加锁的线程都将被阻塞。
- 当读写锁处于读加锁状态时,所有试图以读模式对这个锁访问的线程都将得到访问权,而以写模式对这个锁访问的线程都将被阻塞,需要等到所有读线程释放锁写模式线程才能得到访问权。
- 如果读写锁当前处于读模式,有线程请求写模式,那么接下来的读模式请求都将被阻塞,这样可以避免读模式锁长期被占用,而写模式请求得不到满足。
- 读写锁在使用之前必须初始化,在释放底层内存之前必须被销毁。
-
静态初始化读写锁
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
-
动态初始化读写锁
原函数:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr) 参数一: 要初始化的锁 参数二:attr,锁属性(可写NULL) 返回值:成功返回0,失败返回错误码
-
读模式加锁
原函数:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) 只有一个参数:要加锁的锁 返回值:成功返回0,失败返回错误码
-
尝试读模式加锁
原函数:int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) 只有一个参数:要加锁的锁 返回值::成功返回0,失败返回错误码
-
写模式加锁
原函数:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) 只有一个参数:要加锁的锁 返回值:成功返回0,失败返回错误码
-
尝试写模式加锁
原函数:int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) 只有一个参数:要加锁的锁 返回值:成功返回0,失败返回错误码
-
解锁
原函数:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) 只有一个参数:要解锁的锁 返回值:成功返回0,失败返回错误码
-
读写锁销毁
原函数:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) 只有一个参数:要销毁的锁 返回值:成功返回0,失败返回错误码
- 例子1:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
//定义一个全局锁
pthread_rwlock_t rwlock;
int num=0;
void *thread_fun1(void *arg)
{
//读加锁
// pthread_rwlock_rdlock(&rwlock);
//写加锁
pthread_rwlock_wrlock(&rwlock);
printf("the first thread print %d\n",num);
sleep(5);
printf("the first thread over\n");
//解锁
pthread_rwlock_unlock(&rwlock);
return (void *)1;
}
void *thread_fun2(void *arg)
{
//读加锁
// pthread_rwlock_rdlock(&rwlock);
//写加锁
pthread_rwlock_wrlock(&rwlock);
printf("the second thread print %d\n",num);
sleep(5);
printf("the second thread over\n");
//解锁
pthread_rwlock_unlock(&rwlock);
return (void *)2;
}
int main()
{
pthread_t tid1,tid2;
int err;
//初始化读写锁
err=pthread_rwlock_init(&rwlock,NULL);
if(err != 0)
{
printf("init rwlock failure\n");
return 1;
}
//创建新的线程
err=pthread_create(&tid1,NULL,thread_fun1,NULL);
if(err != 0)
{
printf("create new firest thread failure\n");
return 1;
}
err=pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err != 0)
{
printf("create new second thread failure\n");
return 1;
}
//等待新线程执行完毕
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//读写锁的销毁
pthread_rwlock_destroy(&rwlock);
return 0;
}
运行结果:
the first thread print 0
the first thread over
the second thread print 0
the second thread over
- 例子2:
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;
struct{
pthread_rwlock_t rwlock;
int product;
}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};
void * produce(void *ptr)
{
for (int i = 0; i < 5; ++i)
{
pthread_rwlock_wrlock(&sharedData.rwlock);
sharedData.product = i;
pthread_rwlock_unlock(&sharedData.rwlock);
sleep(1);
}
}
void * consume1(void *ptr)
{
for (int i = 0; i < 5;)
{
pthread_rwlock_rdlock(&sharedData.rwlock);
cout<<"consume1:"<<sharedData.product<<endl;
pthread_rwlock_unlock(&sharedData.rwlock);
++i;
sleep(1);
}
}
void * consume2(void *ptr)
{
for (int i = 0; i < 5;)
{
pthread_rwlock_rdlock(&sharedData.rwlock);
cout<<"consume2:"<<sharedData.product<<endl;
pthread_rwlock_unlock(&sharedData.rwlock);
++i;
sleep(1);
}
}
int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, produce, NULL);
pthread_create(&tid2, NULL, consume1, NULL);
pthread_create(&tid3, NULL, consume2, NULL);
void *retVal;
pthread_join(tid1, &retVal);
pthread_join(tid2, &retVal);
pthread_join(tid3, &retVal);
return 0;
}
执行结果:
consume1:0
consume2:0
consume2:0
consume1:1
consume2:1
consume1:2
consume2:2
consume1:3
consume2:3
consume1:4
待续…
参考链接:
线程的同步-互斥量、读写锁及条件变量
Linux线程同步之读写锁(rwlock)
Linux线程间同步 —— 读写锁(reader-writer lock)
Linux C —— 多线程
多线程编程(Linux C)